public class BrowserCanvas extends JPanel
{
	private static final long serialVersionUID = -8715215920271505397L;

    private GraphicsEngine engine;
    
    /** 
     * Creates a browser canvas using an existing configured rendering engine.
     * @param engine the rendering engine
     */
    public BrowserCanvas(GraphicsEngine engine)
    {
        this.engine = engine;
    }
    
    /**
     * Creates a browser canvas together with a new instance of the browser engine for a document.
     * After creating the engine, the layout itself may be computed by 
     * calling {@link #createLayout(Dimension)}.
     * @param root the &lt;body&gt; element of the document to be rendered
     * @param decoder the CSS decoder used to compute the style
     * @param baseurl the document base URL   
     */
    public BrowserCanvas(org.w3c.dom.Element root, DOMAnalyzer decoder, URL baseurl)
    {
        this(new GraphicsEngine(root, decoder, baseurl));
    }
    
    /** 
     * Creates a browser canvas together with a new instance of the browser engine for a document
     * and creates the layout.
     * 
     * @param root the &lt;body&gt; element of the document to be rendered
     * @param decoder the CSS decoder used to compute the style
     * @param dim the preferred canvas dimensions
     * @param baseurl the document base URL   
     */
    public BrowserCanvas(org.w3c.dom.Element root,
                         DOMAnalyzer decoder,
                         Dimension dim, URL baseurl)
    {
        this(new GraphicsEngine(root, decoder, dim, baseurl));
    }

    /**
     * Gets the rendering engine used in this canvas.
     * @return the rendering engine
     */
    public GraphicsEngine getEngine()
    {
        return engine;
    }

    @Override
    public void paintComponent(Graphics g) 
    {
        super.paintComponent(g);
        g.drawImage(engine.getImage(), 0, 0, null);
    }    

    /**
     * Creates the document layout according to the given viewport size where the visible area size
     * is equal to the whole canvas. If the size of the resulting page is greater than the specified
     * one (e.g. there is an explicit width or height specified for the resulting page), the viewport
     * size is updated automatically.
     * @param dim the viewport size
     */
    public void createLayout(Dimension dim)
    {
        createLayout(dim, new Rectangle(dim));
    }
    
    /**
     * Creates the document layout according to the canvas and viewport size and position. If the size 
     * of the resulting page is greater than the specified one (e.g. there is an explicit width or height
     * specified for the resulting page), the total canvas size is updated automatically.
     * @param dim the total canvas size 
     * @param visibleRect the viewport (the visible area) size and position
     */
    public void createLayout(Dimension dim, Rectangle visibleRect)
    {
        engine.createLayout(dim, visibleRect);
        setPreferredSize(new java.awt.Dimension(engine.getImage().getWidth(), engine.getImage().getHeight()));
        revalidate();
    }
    
    /**
     * Creates the document layout according to the canvas and viewport size and position. If the size 
     * of the resulting page is greater than the specified one (e.g. there is an explicit width or height
     * specified for the resulting page), the total canvas size is updated automatically.
     * @param dim the total canvas size 
     * @param visibleRect the viewport (the visible area) size and position
     */
    public void createLayout(java.awt.Dimension dim, java.awt.Rectangle visibleRect)
    {
        createLayout(new Dimension(dim.width, dim.height),
                new Rectangle(visibleRect.x, visibleRect.y, visibleRect.width, visibleRect.height));
    }
    
    /**
     * Redraws all the rendered boxes.
     */
    public void redrawBoxes()
    {
        engine.redrawBoxes();
        revalidate();
    }

    /**
     * Sets the internal browser engine configuration. This is equal to calling
     * {@link #getEngine()}.{@link Engine#setConfig(BrowserConfig)}.
     * @param config The new browser config.
     */
    public void setConfig(BrowserConfig config)
    {
        engine.setConfig(config);
    }

    /**
     * Gets the internal browser engine configuration. This is equal to calling
     * {@link #getEngine()}.{@link Engine#getConfig()}.
     * @return the browser configuration
     */
    public BrowserConfig getConfig()
    {
        return engine.getConfig();
    }
    
} public class BackgroundDecoder
{
    private static Logger log = LoggerFactory.getLogger(BackgroundDecoder.class);
    
    /** The owning element box */
    private ElementBox owner;
    private VisualContext ctx;
    private NodeData style;
    
    /** Background color or null when transparent */
    private Color bgcolor;
    
    /** Background images or null when there are no background images */
    private List<BackgroundImage> bgimages;
    
    
    /**
     * Creates a background decoder for a given element box.
     * 
     * @param owner The element box that owns the background.
     */
    public BackgroundDecoder(ElementBox owner)
    {
        this.owner = owner;
        ctx = owner.getVisualContext();
        style = owner.getStyle();
        loadBackground();
    }

    /**
     * Returns the element box that owns the background.
     * @return the owner element box
     */
    public ElementBox getOwner()
    {
        return owner;
    }

    /**
     * Obtains the efficient background color to be used for drawing the background.
     * @return the background color or null when transparent
     */
    public Color getBgcolor()
    {
        return bgcolor;
    }

    /**
     * Obtains the list of background images of the element.
     * @return a list of the background images
     */
    public List<BackgroundImage> getBackgroundImages()
    {
        return bgimages;
    }

    /**
     * Checks whether there is no background defined for the owner element.
     * @return {@code true} when the background is empty.
     */
    public boolean isBackgroundEmpty()
    {
        return (bgcolor == null) && (bgimages == null);
    }
    
    /**
     * Loads the background information from the style
     */
    protected void loadBackground()
    {
        if (style != null)
        {
            CSSProperty.BackgroundColor bg = style.getProperty("background-color");
            if (bg == CSSProperty.BackgroundColor.color)
            {
                TermColor bgc = style.getSpecifiedValue(TermColor.class, "background-color");
                if (bgc.isTransparent())
                    bgcolor = null;
                else
                    bgcolor = bgc.getValue();
            }
            else
                bgcolor = null;
    
            bgimages = loadBackgroundImages();
        }
    }
    
    /**
     * Loads background images from the owner element style. Considers their positions, repetition, etc.
     * @return a list of images
     */
    protected List<BackgroundImage> loadBackgroundImages()
    {
        final int count = style.getListSize("background-image", true);
        if (count > 0)
        {
            List<BackgroundImage> bgimages = new ArrayList<BackgroundImage>(count);
            for (int i = 0; i < count; i++)
            {
                final BackgroundImage bgimg = loadBackgroundImage(style, i);
                if (bgimg != null)
                    bgimages.add(bgimg);
            }
            return bgimages.isEmpty() ? null : bgimages; //return null when there are no images
        }
        else
            return null;
    }
    
    /**
     * Creates background a image structure based on a style definition.
     * @param style the style definition
     * @param index index of the image to take (there may be multiple images defined, 0 for the first image defined)
     * @return The corresponding BackgroundImage object.
     */
    protected BackgroundImage loadBackgroundImage(NodeData style, int index)
    {
        CSSProperty.BackgroundImage image = style.getProperty("background-image", index);
        if (image == CSSProperty.BackgroundImage.uri || image == CSSProperty.BackgroundImage.gradient)
        {
            try {
                BackgroundImage ret = null;
                //position
                CSSProperty.BackgroundPosition position = style.getProperty("background-position", index);
                TermList positionValues = style.getValue(TermList.class, "background-position", index);
                //repeat
                CSSProperty.BackgroundRepeat repeat = style.getProperty("background-repeat", index);
                if (repeat == null) repeat = BackgroundRepeat.REPEAT;
                //origin
                CSSProperty.BackgroundOrigin origin = style.getProperty("background-origin", index);
                if (origin == null) origin = BackgroundOrigin.PADDING_BOX;
                //attachment
                CSSProperty.BackgroundAttachment attachment = style.getProperty("background-attachment", index);
                if (attachment == null) attachment = BackgroundAttachment.SCROLL;
                //size
                CSSProperty.BackgroundSize size = style.getProperty("background-size", index);
                TermList sizeValues = null;
                if (size == null) size = BackgroundSize.list_values;
                else if (size == BackgroundSize.list_values)
                    sizeValues = style.getValue(TermList.class, "background-size", index);
                //image
                if (image == CSSProperty.BackgroundImage.uri)
                {
                    TermURI urlstring = style.getValue(TermURI.class, "background-image", index);
                    URL url = DataURLHandler.createURL(urlstring.getBase(), urlstring.getValue());
                    BackgroundImageImage bgimg =
                            new BackgroundImageImage(owner, url, position, positionValues, repeat, 
                                    attachment, origin, size, sizeValues);
                    if (ctx.getConfig().getLoadBackgroundImages())
                        bgimg.setImage(ctx.getImageLoader().loadImage(url));
                    ret = bgimg;
                }
                else if (image == CSSProperty.BackgroundImage.gradient)
                {
                    BackgroundImageGradient bgimg =
                            new BackgroundImageGradient(owner, position, positionValues, repeat, 
                                    attachment, origin, size, sizeValues);
                    Rectangle bgsize = bgimg.getComputedPosition();
                    
                    TermFunction.Gradient values = style.getValue(TermFunction.Gradient.class, "background-image", index);
                    if (values instanceof TermFunction.LinearGradient)
                    {
                        TermFunction.LinearGradient spec = (TermFunction.LinearGradient) values;
                        LinearGradient grad = new LinearGradient();
                        grad.setRepeating(spec.isRepeating());
                        double angle = (spec.getAngle() != null) ? ctx.degAngle(spec.getAngle()) : 180.0;
                        grad.setAngleDeg(angle, bgsize.width, bgsize.height);
                        addStopsToGradient(grad, spec.getColorStops());
                        bgimg.setGradient(grad);
                        ret = bgimg;
                    }
                    else if (values instanceof TermFunction.RadialGradient)
                    {
                        TermFunction.RadialGradient spec = (TermFunction.RadialGradient) values;
                        RadialGradient grad = new RadialGradient(bgsize);
                        grad.setRepeating(spec.isRepeating());
                        CSSDecoder dec = new CSSDecoder(ctx);
                        float px = dec.getLength(spec.getPosition()[0], false, 0, 0, bgsize.width);
                        float py = dec.getLength(spec.getPosition()[1], false, 0, 0, bgsize.height);
                        if ("circle".equals(spec.getShape().getValue()))
                        {
                            RadialGradient.GradientSize ident = RadialGradient.decodeSizeIdent(spec.getSizeIdent());
                            if (ident != null)
                            {
                                grad.setCircleDataRadLengths(ident, px, py);
                            }
                            else
                            {
                                //actually, percentages are not allowed here but they could be in the future
                                // https://drafts.csswg.org/css-images-3/#typedef-size
                                float r = dec.getLength(spec.getSize()[0], false, 0, 0, bgsize.width);
                                grad.setCircle(r, px, py);
                            }
                        }
                        else //ellipse
                        {
                            RadialGradient.GradientSize ident = RadialGradient.decodeSizeIdent(spec.getSizeIdent());
                            if (ident != null)
                            {
                                grad.setEllipse(ident, px, py);
                            }
                            else
                            {
                                float rx = dec.getLength(spec.getSize()[0], false, 0, 0, bgsize.width);
                                float ry = dec.getLength(spec.getSize()[1], false, 0, 0, bgsize.height);
                                grad.setEllipse(rx, ry, px, py);
                            }
                        }
                        addStopsToGradient(grad, spec.getColorStops());
                        bgimg.setGradient(grad);
                        ret = bgimg;
                    }
                }
                return ret;
            } catch (MalformedURLException e) {
                log.warn(e.getMessage());
                return null;
            }
        }
        else
            return null;
    }

    /**
     * Extracts the stops
     * @param grad
     * @param spec
     */
    private void addStopsToGradient(Gradient grad, List<TermFunction.Gradient.ColorStop> stops)
    {
        if (stops != null)
        {
            CSSDecoder dec = new CSSDecoder(ctx);
            for (TermFunction.Gradient.ColorStop stop : stops)
            {
                Color color = stop.getColor().getValue();
                Float percentage = null;
                Float pxLength = null;
                TermLengthOrPercent spec = stop.getLength();
                if (spec != null)
                {
                    if (spec.isPercentage())
                        percentage = spec.getValue();
                    else
                        pxLength = dec.getLength(spec, false, 0, 0, 0);
                }
                grad.addStop(new GradientStop(color, percentage, pxLength));
            }
        }
        grad.recomputeStops();
    }
    
} public class CSSNorm 
{

    /**
     * Defines a standard HTML style sheet defining the basic style of the individual elements.
     * It corresponds to the CSS2.1 recommendation (Appendix D) with some additions for
     * new HTML5 elements.
     * @return the style string
     */
    public static String stdStyleSheet()
    {
        try {
            return getResource("css/ua-std.css") + getResource("css/ua-html5.css");
        } catch (IOException e) {
            return "";
        }
    }

    /**
     * A style sheet defining the additional basic style not covered by the CSS recommendation.
     * @return The style string
     */
    public static String userStyleSheet()
    {
        try {
            return getResource("css/ua-ext.css");
        } catch (IOException e) {
            return "";
        }
    }
    
    /**
     * A style sheet defining a basic style of form fields. This style sheet may be used
     * for a simple rendering of form fields when their functionality is not implemented
     * in another way.
     * @return The style string
     */
    public static String formsStyleSheet()
    {
        try {
            return getResource("css/ua-forms.css");
        } catch (IOException e) {
            return "";
        }
    }
    
    /**
     * Loads an internal style sheet from resource. 
     * @param name
     * @return
     * @throws IOException
     */
    private static String getResource(String name) throws IOException
    {
        try (InputStream is = CSSNorm.class.getResourceAsStream("/" + name))
        {
            if (is != null) 
            {
                final Scanner scan = new Scanner(is, "UTF-8");
                scan.useDelimiter("\\A");
                final String ret = scan.next();
                scan.close();
                return ret;
            }
            else
                return "";
        }
    }
    
} public class CSSUnits 
{
    public static final float dpi = 96; //CSS3 assumes fixed 96 dpi
    public static final float medium_font = 12; //12pt
    private static final float font_step = 1.2f;
    public static final float THIN_BORDER = 1.0f;
    public static final float MEDIUM_BORDER = 3.0f;
    public static final float THICK_BORDER = 5.0f;
    
    /** Converts points to pixels according to the DPI set */
    public static float pixels(float pt)
    {
        return pt * dpi / 72.0f; 
    }

    /** Converts pixels to points according to the DPI set */
    public static float points(float px)
    {
        return px * 72.0f / dpi;
    }
    
    /** 
     * Converts the font size given by an identifier to absolute length in pixels.
     * @param parent Parent font size (taken as 1em)
     * @param value The size specification to be converted
     * @return absolute font size in px
     */
    public static float convertFontSize(float parent, CSSProperty.FontSize value)
    {
        final float em = parent;
        float ret = em;
        if (value == CSSProperty.FontSize.MEDIUM)
            ret = medium_font;
        else if (value == CSSProperty.FontSize.SMALL)
            ret = medium_font / font_step;
        else if (value == CSSProperty.FontSize.X_SMALL)
            ret = medium_font / font_step / font_step;
        else if (value == CSSProperty.FontSize.XX_SMALL)
            ret = medium_font / font_step / font_step / font_step;
        else if (value == CSSProperty.FontSize.LARGE)
            ret = medium_font * font_step;
        else if (value == CSSProperty.FontSize.X_LARGE)
            ret = medium_font * font_step * font_step;
        else if (value == CSSProperty.FontSize.XX_LARGE)
            ret = medium_font * font_step * font_step * font_step;
        else if (value == CSSProperty.FontSize.SMALLER)
            ret = em / font_step;
        else if (value == CSSProperty.FontSize.LARGER)
            ret = em * font_step;
        return ret;
    }
    
    /**
     * Converts the border size given by an identifier to an absolute value.
     * @param width the border-width identifier
     * @return absolute length in pixels
     */
    public static float convertBorderWidth(CSSProperty.BorderWidth width)
    {
    	if (width == CSSProperty.BorderWidth.THIN)
    		return THIN_BORDER;
    	else if (width == CSSProperty.BorderWidth.MEDIUM)
    		return MEDIUM_BORDER;
    	else
    		return THICK_BORDER;
    }
    
} public class DOMAnalyzer 
{
    private static Logger log = LoggerFactory.getLogger(DOMAnalyzer.class);
	private static final String DEFAULT_MEDIA = "screen";
	
    private Document doc;   //the root node of the DOM tree
    private URL baseUrl;    //base URL
    private MediaSpec media;   //media type
    private String encoding; //default character encoding for style sheet parsing
    
    private Vector<StyleSheet> styles;  //vector of StyleSheet sheets
    private Analyzer analyzer; //style sheet analyzer
    private StyleMap stylemap; //style map for DOM nodes
    private StyleMap istylemap; //style map with inheritance
    private FontTable fontTable; //local font table
    
    /** The origin of a style sheet */
    public enum Origin 
    {
    	/** An author style sheet (this is the default in most cases) */
    	AUTHOR, 
    	/** A user agent style sheet */
    	AGENT, 
    	/** A user tstyle sheet */
    	USER 
    }
    
    /**
     * Creates a new DOM analyzer.
     * @param doc the document to be analyzed
     */
    public DOMAnalyzer(org.w3c.dom.Document doc) 
    {
        this.doc = doc;
        this.media = new MediaSpec(DEFAULT_MEDIA);
        baseUrl = null;
        encoding = null;
        styles = new Vector<StyleSheet>();
        stylemap = null;
        istylemap = null;
    }
        
    /**
     * Creates a new DOM analyzer.
     * @param doc the document to be analyzed
     * @param baseUrl the base URL for loading the style sheets. This URL may be redefined by the <code>&lt;base&gt;</code> tag used in the
     * document header.
     */
    public DOMAnalyzer(org.w3c.dom.Document doc, URL baseUrl) 
    {
        this(doc, baseUrl, true);
    }
    
    /**
     * Creates a new DOM analyzer.
     * @param doc the document to be analyzed
     * @param baseUrl the base URL for loading the style sheets. If <code>detectBase</code>, this URL may be redefined by the <code>&lt;base&gt;</code> tag used in the
     * document header.
     * @param detectBase sets whether to try to accept the <code>&lt;base&gt;</code> tags in the document header.
     */
    public DOMAnalyzer(org.w3c.dom.Document doc, URL baseUrl, boolean detectBase) 
    {
        this.doc = doc;
        this.encoding = null;
        this.media = new MediaSpec(DEFAULT_MEDIA);
        styles = new Vector<StyleSheet>();
        this.baseUrl = baseUrl;
        if (detectBase)
        {
            String docbase = getDocumentBase();
            if (docbase != null)
            {
                try {
                    this.baseUrl = new URL(baseUrl, docbase);
                    log.info("Using specified document base " + this.baseUrl);
                } catch (MalformedURLException e) {
                    log.warn("Malformed base URL " + docbase);
                }
            }
        }
        stylemap = null;
        istylemap = null;
    }

    /**
     * Obtains the default character encoding used for parsing the style sheets. This encoding is used when there is
     * no other encoding specified inside the style sheet using the <code>@charset</code> rule. When there
     * is no default encoding specified, this method returns <code>null</code> and the system default encoding
     * is used for style sheets.
     * @return The encoding name or <code>null</code> when there is no default encoding specified.
     */
    public String getDefaultEncoding()
    {
        return encoding;
    }

    /**
     * Sets the default character encoding used for parsing the referenced style sheets. This encoding is used when there is
     * no other encoding specified inside the style sheet using the <code>@charset</code> rule.
     * @param encoding The encoding string or <code>null</code> for using the system default encoding.
     */
    public void setDefaultEncoding(String encoding)
    {
        this.encoding = encoding;
    }

    /**
     * Returns the type of the current medium used.
	 * @return the media type according to CSS
	 */
	public String getMedia()
	{
		return media.getType();
	}

	/**
	 * Set the type of the medium used for computing the style. Default is "screen".
	 * @param media the medium to set
	 */
	public void setMedia(String media)
	{
		this.media = new MediaSpec(media);
	}

    /**
     * Returns the full specification of the currently used medium.
     * @return the media specification according to CSS
     */
    public MediaSpec getMediaSpec()
    {
        return media;
    }

    /**
     * Set the specification of the medium used for computing the style. The default is "screen" with
     * some standard features that correspond to a normal desktop station.
     * @param media the medium to set
     */
    public void setMediaSpec(MediaSpec media)
    {
        this.media = media;
    }

	/**
	 * Returns the root element of the document.
	 */
	public Element getRoot()
	{
	    return doc.getDocumentElement();
	}
	
	/**
     * Returns the &lt;head&gt; element.
     */
    public Element getHead()
    {
        return (Element) doc.getElementsByTagName("head").item(0);
    }
    
    /**
     * Returns the &lt;body&gt; element.
     */
    public Element getBody()
    {
        return (Element) doc.getElementsByTagName("body").item(0);
    }

    /**
     * Finds the explicitly specified base URL in the document according to the HTML specification
     * @return the base URL string, when present in the document or null when not present
     * @see <a href="http://www.w3.org/TR/html4/struct/links.html#edef-BASE">the BASE element definition</a>
     */
    public String getDocumentBase()
    {
        Element head = getHead();
        if (head != null)
        {
            NodeList bases = head.getElementsByTagName("base");
            if (bases != null && bases.getLength() > 0)
            {
                Element base = (Element) bases.item(0);
                if (base.hasAttribute("href"))
                    return base.getAttribute("href");
                else
                    return null;
            }
            else
                return null;
        }
        else
            return null;
    }
    
    /**
     * Finds the explicitly specified character encoding using the <code>&lt;meta&gt;</code> tag according
     * to the HTML specifiaction.
     * @return the encoding name when present in the document or <code>null</code> if not present. 
     */
    public String getCharacterEncoding()
    {
        Element head = getHead();
        if (head != null)
        {
            NodeList metas = head.getElementsByTagName("meta");
            if (metas != null)
            {
                for (int i = 0; i < metas.getLength(); i++)
                {
                    Element meta = (Element) metas.item(i);
                    if (meta.hasAttribute("http-equiv")
                            && meta.getAttribute("http-equiv").equalsIgnoreCase("content-type") 
                            && meta.hasAttribute("content"))
                    {
                        String ctype = meta.getAttribute("content").toLowerCase(); 
                        int cpos = ctype.indexOf("charset=");
                        if (cpos != -1)
                            return ctype.substring(cpos + 8);
                    }
                    else if (meta.hasAttribute("charset"))
                    {
                        return meta.getAttribute("charset");
                    }
                }
                return null;
            }
            else
                return null;
        }
        else
            return null;
    }
    
    /**
     * Formats the tag tree to an output stream. This is used mainly for debugging purposes.
     */
    public void printTagTree(java.io.PrintStream out)
    {
        recursivePrintTags(doc, 0, out);
    }

    /**
     * Encodes the efficient style of all the elements to their <code>style</code> attributes.
     * Inheritance is not applied.
     * This is currently not necessary for the rendering. It is practical mainly together with
     * the <code>printTagTree</code> method for debugging the resulting style. 
     */
    public void stylesToDom()
    {
        recursiveStylesToDom(getBody());
    }
    
    /**
     * Encodes the efficient style of all the elements to their <code>style</code> attributes
     * while applying the inheritance.
     * This is currently not necessary for the rendering. It is practical mainly together with
     * the <code>printTagTree</code> method for debugging the resulting style. 
     */
    public void stylesToDomInherited()
    {
        recursiveStylesToDomInherited(getBody());
    }

    /**
     * Converts the HTML presentation attributes in the document body to inline styles.
     */
    public void attributesToStyles()
    {
        //fix the DOM tree structure according to HTML syntax
        HTMLNorm.normalizeHTMLTree(doc);
        //convert attributes to style definitions
        Element body = getBody();
        if (body != null)
            HTMLNorm.attributesToStyles(body, "");
        else
            log.error("No <body> element found in the DOM.");
    }
    
    /**
     * Removes all the external style sheet links and puts the whole style sheet locally
     * to the document head.
     */
    public void localizeStyles()
    {
        //remove the style definitions
        Vector<Element> elems = new Vector<Element>();
        recursiveFindStyleElements(getRoot(), elems);
        for (Element e : elems)
            e.getParentNode().removeChild(e);
        //create a new style element in the header
        Element style = doc.createElement("style");
        style.setAttribute("type", "text/css");
        getHead().appendChild(style);
        //fill it with the style text
        StringBuilder sb = new StringBuilder();
        for (StyleSheet sheet : styles)
        {
            for (Object o : sheet.asList())
            {
                if (o instanceof RuleSet)
                {
                    //check the first part of the first selector
                    Selector.SelectorPart start = ((RuleSet) o).getSelectors()[0].get(0).get(0);
                    //skip inline styles
                    if (!(start instanceof Selector.ElementDOM))
                        sb.append(o.toString());
                }
            }
        }
        Text styletext = doc.createTextNode(sb.toString());
        style.appendChild(styletext);
    }
    
    /** 
     * Returns a vector of CSSStyleSheet objects referenced from the document for the specified
     * media type with default values of the remaining media features. The internal style sheets
     * are read from the document directly, the external ones are downloaded and parsed automatically.
     * @param media the media type string
     */
    public void getStyleSheets(String media)
    {
    	this.media = new MediaSpec(media);
        StyleSheet newsheet = CSSFactory.getUsedStyles(doc, encoding, baseUrl, this.media);
        styles.add(newsheet);
    }

    /** 
     * Returns a vector of CSSStyleSheet objects referenced from the document for the specified
     * media type and features. The internal style sheets are read from the document directly, the external
     * ones are downloaded and parsed automatically.
     * @param media the media specification
     */
    public void getStyleSheets(MediaSpec media)
    {
        this.media = media;
        StyleSheet newsheet = CSSFactory.getUsedStyles(doc, encoding, baseUrl, this.media);
        styles.add(newsheet);
    }

    /** 
     * Returns a vector of CSSStyleSheet objects referenced from the document for the media
     * type set by <code>setMedia()</code> (or "screen" by default). The internal style 
     * sheets are read from the document directly, the external ones are downloaded
     * and parsed automatically.
     */
    public void getStyleSheets()
    {
    	getStyleSheets(media);
    }
    
    /**
     * Loads a stylesheet from an URL as an author style sheet.
     * Imports all the imported style sheets before storing it.
     * @param base the document base url
     * @param href the href specification
     * @param encoding the default encoding
     */
    public void loadStyleSheet(URL base, String href, String encoding)
    {
    	loadStyleSheet(base, href, encoding, Origin.AUTHOR);
    }
    
    /**
     * Loads a stylesheet from an URL as an author style sheet
     * using the network processor to obtain the URL
     * Imports all the imported style sheets before storing it.
     * @param base the document base url
     * @param href the href specification
     * @param encoding the default encoding
     * @param processor the network processor used for handling the URL connections
     */
    public void loadStyleSheet(URL base, String href, String encoding, NetworkProcessor processor)
    {
    	loadStyleSheet(base, href, encoding, Origin.AUTHOR, processor);
    }
    
    /**
     * Loads a stylesheet from an URL and specifies the origin.
     * Imports all the imported style sheets before storing it.
     * @param base the document base url
     * @param href the href specification
     * @param encoding the default character encoding for the style sheet (use <code>null</code> for default system encoding)
     * @param origin the style sheet origin (author, user agent or user)
     */
    public void loadStyleSheet(URL base, String href, String encoding, Origin origin)
    {
        try {
            StyleSheet newsheet = CSSFactory.parse(new URL(base, href), encoding);
            newsheet.setOrigin(translateOrigin(origin));
            styles.add(newsheet);
        } catch (IOException e) {
            log.error("I/O Error: "+e.getMessage());
        } catch (CSSException e) {
            log.error("CSS Error: "+e.getMessage());
        }
    }
    
    /**
     * Loads a stylesheet from an URL and specifies the origin and the network
	 * processor used to obtain the content of the url.
	 * Imports all the imported style sheets before storing it.
	 * @param base the document base url
     * @param href the href specification
     * @param encoding the default character encoding for the style sheet (use <code>null</code> for default system encoding)
     * @param origin the style sheet origin (author, user agent or user)
     * @param processor the network processor used for handling the URL connections
     */
    public void loadStyleSheet(URL base, String href, String encoding, Origin origin, NetworkProcessor processor)
    {
        try {
            StyleSheet newsheet = CSSFactory.parse(new URL(base, href), processor, encoding);
            newsheet.setOrigin(translateOrigin(origin));
            styles.add(newsheet);
        } catch (IOException e) {
            log.error("I/O Error: "+e.getMessage());
        } catch (CSSException e) {
            log.error("CSS Error: "+e.getMessage());
        }
    }
    
    /**
     * Parses and adds an author style sheet represented as a string to the end of the used
     * stylesheet lists. It imports all the imported style sheets before storing
     * it.
     * @param base the document base URL
     * @param cssdata the style string
     * @deprecated This method does not specify the style sheet origin; use {@link #addStyleSheet(URL, String, Origin)} instead.
     */
	public void addStyleSheet(URL base, String cssdata)
    {
		addStyleSheet(base, cssdata, Origin.AUTHOR);
    }
    
    /**
     * Parses and adds an author style sheet represented as a string to the end of the used
     * stylesheet lists. It imports all the imported style sheets before storing
     * it. The origin of the style sheet is set to the author, agent or user. The origin
     * influences the rule priority according to the <a href="http://www.w3.org/TR/CSS21/cascade.html#cascading-order">CSS specification</a>.
     * @param base the document base URL
     * @param cssdata the style string
     * @param origin the style sheet origin (AUTHOR, AGENT or USER)
     */
	public void addStyleSheet(URL base, String cssdata, Origin origin)
    {
	    try {
    	    StyleSheet newsheet = CSSFactory.parseString(cssdata, base);
            newsheet.setOrigin(translateOrigin(origin));
            styles.add(newsheet);
            fontTable = null; //force recomputing the font table the next time
	    } catch (IOException e) {
            log.error("I/O Error: "+e.getMessage());
        } catch (CSSException e) {
            log.error("DOMAnalyzer: CSS Error: "+e.getMessage());
        }
    }
	
    /**
     * Parses and adds an author style sheet represented as a string to the end of the used
     * stylesheet lists. It imports all the imported style sheets before storing
     * it, using the network processor to obtain the URL. The origin of the style sheet is set to the author, agent or user. The origin
     * influences the rule priority according to the <a href="http://www.w3.org/TR/CSS21/cascade.html#cascading-order">CSS specification</a>.
     * @param base the document base URL
     * @param cssdata the style string
     * @param origin the style sheet origin (AUTHOR, AGENT or USER)
     * @param processor the network processor used for handling the URL connections
     */
	public void addStyleSheet(URL base, String cssdata, Origin origin, NetworkProcessor processor)
    {
	    try {
    	    StyleSheet newsheet = CSSFactory.parseString(cssdata, base, processor);
            newsheet.setOrigin(translateOrigin(origin));
            styles.add(newsheet);
            fontTable = null; //force recomputing the font table the next time
	    } catch (IOException e) {
            log.error("I/O Error: "+e.getMessage());
        } catch (CSSException e) {
            log.error("DOMAnalyzer: CSS Error: "+e.getMessage());
        }
    }
	
	/**
	 * Forces recomputing the element styles. This method should be called when a new style sheet
	 * has been added or a DOM has changed after some styles have been read from the analyzer.
	 */
	public void recomputeStyles()
	{
	    //this forces re-creating the analyzer and stylemap upon next read
	    analyzer = null;
	    stylemap = null;
	    istylemap = null;
	}
	
    /**
     * Gets all the style declarations for a particular element and computes 
     * the resulting element style.
     * @param el the element for which the style should be computed
     * @return the resulting declaration
     */
    public NodeData getElementStyle(Element el)
    {
    	if (analyzer == null)
    		analyzer = new Analyzer(styles);
    	
    	if (stylemap == null)
    		stylemap = analyzer.evaluateDOM(doc, media, false);
    	
    	return stylemap.get(el);
    }
    
    /**
     * Checks whether the inhetited style has been computed and computes it when necessary.
     */
    private void checkStylesInherited()
    {
    	if (analyzer == null)
    		analyzer = new Analyzer(styles);
        
        if (istylemap == null)
            istylemap = analyzer.evaluateDOM(doc, media, true);
    }
    
    /**
     * Gets all the style declarations for a particular element and computes 
     * the resulting element style including the inheritance from the parent.
     * @param el the element for which the style should be computed
     * @return the resulting style declaration 
     */
    public NodeData getElementStyleInherited(Element el)
    {
        checkStylesInherited();
    	return istylemap.get(el);
    }
    
    /**
     * Gets all the style declarations for a particular element and a particular pseudo-element and computes 
     * the resulting pseudo-element style including the inheritance from the parent.
     * @param el the element for which the style should be computed
     * @param pseudo the pseudo class or element used for style computation
     * @return the resulting style declaration 
     */
    public NodeData getElementStyleInherited(Element el, PseudoElementType pseudo)
    {
        checkStylesInherited();
        return istylemap.get(el, pseudo);
    }
    
    /**
     * Checks whether the element has some style defined for a specified pseudo-element. 
     * @param el The element to be checked
     * @param pseudo The pseudo element specification
     * @return <code>true</code> if there is some style defined for the given pseudo-element
     */
    public boolean hasPseudoDef(Element el, PseudoElementType pseudo)
    {
        checkStylesInherited();
        return istylemap.hasPseudo(el, pseudo);
    }
    
    /**
     * Assigns the given style for the specified pseudo-element.
     * @param el the element to be assigned the style
     * @param pseudo The pseudo-element or <code>null</code> if none is required
     * @param style the assigned style
     */
    public void useStyle(Element el, PseudoElementType pseudo, NodeData style)
    {
        checkStylesInherited();
        istylemap.put(el, pseudo, style);
    }
    
    public FontTable getFontTable()
    {
        if (fontTable == null)
        {
            fontTable = new FontTable();
            scanLocalFonts();
        }
        return fontTable;
    }
    
    private void scanLocalFonts()
    {
        for (StyleSheet sheet : styles)
        {
            for (RuleBlock<?> block : sheet.asList())
            {
                if (block instanceof RuleFontFace)
                {
                    processFontFaceRule((RuleFontFace) block);
                }
            }
        }
    }
    
    private void processFontFaceRule(RuleFontFace rule)
    {
        FontSpec spec = new FontSpec(rule.getFontFamily(), rule.getFontWeight(), rule.getFontStyle());
        List<RuleFontFace.Source> sources = rule.getSources();
        if (sources != null)
            fontTable.put(spec, sources);
    }

    //====================================================================
    
    private void recursiveStylesToDom(Node n)
    {
        if (n.getNodeType() == Node.ELEMENT_NODE)
        {
            Element el = (Element) n;
            NodeData decl = getElementStyle(el);
            if (decl != null)
            {
                String decls = decl.toString().replace("\n", "");
                if (!decls.isEmpty())
                    el.setAttribute("style", quote(decls));
            }
        }                
        NodeList child = n.getChildNodes();
        for (int i = 0; i < child.getLength(); i++)
            recursiveStylesToDom(child.item(i));
    }

    private void recursiveStylesToDomInherited(Node n)
    {
        if (n.getNodeType() == Node.ELEMENT_NODE)
        {
            Element el = (Element) n;
            NodeData decl = getElementStyleInherited(el);
            if (decl != null)
            {
                String decls = decl.toString().replace("\n", "");
                el.setAttribute("style", quote(decls));
            }
        }                
        NodeList child = n.getChildNodes();
        for (int i = 0; i < child.getLength(); i++)
            recursiveStylesToDomInherited(child.item(i));
    }

    /**
     * Finds all the style definitions in the document.
     */
    private void recursiveFindStyleElements(Element e, Vector<Element> elems)
    {
        if ("style".equalsIgnoreCase(e.getNodeName()) ||
            ("link".equalsIgnoreCase(e.getNodeName()) && "stylesheet".equalsIgnoreCase(e.getAttribute("rel"))))
        {
            elems.add(e);
        }
        else
        {
            NodeList list = e.getChildNodes();
            for (int i = 0; i < list.getLength(); i++)
            {
                if (list.item(i).getNodeType() == Node.ELEMENT_NODE)
                    recursiveFindStyleElements((Element) list.item(i), elems);
            }
        }
    }
    
    private String quote(String s)
    {
        return s.replace('"', '\'');
    }

    //========================================================================
    
    private void recursivePrintTags(Node n, int level, java.io.PrintStream p)
    {
        String mat = "";
        
        if (n.getNodeType() == Node.ELEMENT_NODE)
        {
            NodeData decl = getElementStyle((Element) n);
            if (decl != null)
                mat = "style:\"" + decl.toString() + "\"";
        }
        else if (n.getNodeType() == Node.TEXT_NODE)
        {
            mat = n.getNodeValue();
        }
                
        String ind = "";
        for (int i = 0; i < level*4; i++) ind = ind + ' ';
        
        p.println(ind + n.getNodeName() + " " + mat);
        
        
        NodeList child = n.getChildNodes();
        for (int i = 0; i < child.getLength(); i++)
            recursivePrintTags(child.item(i), level+1, p);   
    }
    
    /**
     * Translates the origin from the CSSBox API to jStyleParser API
     * (in order not to expose the jStyleParser API in CSSBox)
     */
    private StyleSheet.Origin translateOrigin(Origin origin)
    {
    	if (origin == Origin.AUTHOR)
    		return StyleSheet.Origin.AUTHOR;
    	else if (origin == Origin.AGENT)
    		return StyleSheet.Origin.AGENT;
    	else
    		return StyleSheet.Origin.USER;
    }
} public abstract class DOMSource
{
    protected DocumentSource src;
    protected String charset;

    public DOMSource(DocumentSource src)
    {
        this.src = src;
        setContentType(src.getContentType());
    }

    public DocumentSource getDocumentSource()
    {
        return src;
    }

    public String getCharset()
    {
        return charset;
    }

    public void setContentType(String type)
    {
        if (type != null)
        {
            String t = type.toLowerCase();

            //extract the charset if specified
            int strt = t.indexOf("charset=");
            if (strt >= 0)
            {
                strt += "charset=".length();
                int stop = t.indexOf(';', strt);
                if (stop == -1)
                    stop = t.length();
                charset = t.substring(strt, stop).trim();
                charset = charset.replaceAll("^\"|\"$|^\'|\'$", "").trim();
            }
        }
    }

    abstract public Document parse() throws SAXException, IOException;
    
} public class DefaultDOMSource extends DOMSource
{
    static boolean neko_fixed = false;

    public DefaultDOMSource(DocumentSource src)
    {
        super(src);
    }

    @Override
    public Document parse() throws SAXException, IOException
    {
        //temporay NekoHTML fix until nekohtml gets fixed
        if (!neko_fixed)
        {
            HTMLElements.Element li = HTMLElements.getElement(HTMLElements.LI);
            HTMLElements.Element[] oldparents = li.parent;
            li.parent = new HTMLElements.Element[oldparents.length + 1];
            for (int i = 0; i < oldparents.length; i++)
                li.parent[i] = oldparents[i];
            li.parent[oldparents.length] = HTMLElements.getElement(HTMLElements.MENU);
            neko_fixed = true;
        }
        
        DOMParser parser = new DOMParser(new HTMLConfiguration());
        parser.setProperty("http://cyberneko.org/html/properties/names/elems", "lower");
        if (charset != null)
            parser.setProperty("http://cyberneko.org/html/properties/default-encoding", charset);
        parser.parse(new org.xml.sax.InputSource(getDocumentSource().getInputStream()));
        return parser.getDocument();
    }

} public class DefaultDocumentSource extends DocumentSource
{
    /** The user-agent string used for HTTP connection */
    private static String USER_AGENT = "Mozilla/5.0 (compatible; BoxBrowserTest/4.x; Linux) CSSBox/4.x (like Gecko)";

    private URLConnection con;
    private InputStream is;
    
    /**
     * Creates a network data source based on the target document URL.
     * @param url the document URL
     * @throws IOException
     */
    public DefaultDocumentSource(URL url) throws IOException
    {
        super(url);
        con = createConnection(url);
        is = null;
    }
    
    /**
     * Creates a data source based on the URL string. The data: urls are automatically
     * recognized and  processed.
     * @param urlstring The URL string
     * @throws IOException
     */
    public DefaultDocumentSource(String urlstring) throws IOException
    {
        super(null, urlstring);
        URL url = DataURLHandler.createURL(null, urlstring);
        con = createConnection(url);
        is = null;
    }
    
    /**
     * Creates a data source based on the URL string. The data: urls are automatically
     * recognized and  processed.
     * @param base The base URL to be used for the relative URLs in the urlstring
     * @param urlstring The URL string
     * @throws IOException
     */
    public DefaultDocumentSource(URL base, String urlstring) throws IOException
    {
        super(base, urlstring);
        URL url = DataURLHandler.createURL(base, urlstring);
        con = createConnection(url);
        is = null;
    }
    
    /**
     * Creates and configures the URL connection.
     * @param url the target URL
     * @return the created connection instance
     * @throws IOException
     */
    protected URLConnection createConnection(URL url) throws IOException
    {
        URLConnection con = url.openConnection();
        con.setRequestProperty("User-Agent", USER_AGENT);
        return con;
    }
    
    @Override
    public URL getURL()
    {
        return con.getURL();
    }
    
    @Override
    public InputStream getInputStream() throws IOException
    {
        if (is == null)
            is = con.getInputStream();
        return is;
    }
    
    @Override
    public String getContentType()
    {
        return con.getHeaderField("Content-Type");
    }

    /**
     * Obtains the current User-agent string used for new connections.
     * @return the user-agent string.
     */
    public static String getUserAgent()
    {
        return USER_AGENT;
    }

    /**
     * Sets the user agent string that will be used for new connections.
     * @param userAgent the user-agent string
     */
    public static void setUserAgent(String userAgent)
    {
        USER_AGENT = userAgent;
    }

    @Override
    public void close() throws IOException
    {
        if (is != null)
            is.close();
    }
    

} public abstract class DocumentSource implements java.io.Closeable
{
    
    /**
     * Creates a new document source from an URL.
     * @param url the document URL
     * @throws IOException
     */
    public DocumentSource(URL url) throws IOException
    {
    }
    
    /**
     * Creates a new document source based on a string representation of the URL.
     * @param urlstring the URL string
     * @throws IOException
     */
    public DocumentSource(URL base, String urlstring) throws IOException
    {
    }
    
    /**
     * Obtains the final URL of the obtained document. This URL may be different
     * from the URL used for DocumentSource construction, e.g. when some HTTP redirect
     * occurs.
     * @return the document URL.
     */
    abstract public URL getURL();
    
    /**
     * Obtains the MIME content type of the target document.
     * @return a MIME string.
     */
    abstract public String getContentType();
    
    /**
     * Obtains the input stream for reading the referenced document.
     * @return The input stream.
     * @throws IOException
     */
    abstract public InputStream getInputStream() throws IOException;

    /**
     * Closes the document source.
     */
    abstract public void close() throws IOException;
    
} public class BlockBox extends ElementBox
{
    private static Logger log = LoggerFactory.getLogger(BlockBox.class);
    
    public static final CSSProperty.Float FLOAT_NONE = CSSProperty.Float.NONE;
    public static final CSSProperty.Float FLOAT_LEFT = CSSProperty.Float.LEFT;
    public static final CSSProperty.Float FLOAT_RIGHT = CSSProperty.Float.RIGHT;
    
    public static final CSSProperty.Clear CLEAR_NONE = CSSProperty.Clear.NONE;
    public static final CSSProperty.Clear CLEAR_LEFT = CSSProperty.Clear.LEFT;
    public static final CSSProperty.Clear CLEAR_RIGHT = CSSProperty.Clear.RIGHT;
    public static final CSSProperty.Clear CLEAR_BOTH = CSSProperty.Clear.BOTH;
    
    public static final CSSProperty.TextAlign ALIGN_LEFT = CSSProperty.TextAlign.LEFT;
    public static final CSSProperty.TextAlign ALIGN_RIGHT = CSSProperty.TextAlign.RIGHT;
    public static final CSSProperty.TextAlign ALIGN_CENTER = CSSProperty.TextAlign.CENTER;
    public static final CSSProperty.TextAlign ALIGN_JUSTIFY = CSSProperty.TextAlign.JUSTIFY;
    
    public static final CSSProperty.Overflow OVERFLOW_VISIBLE = CSSProperty.Overflow.VISIBLE;
    public static final CSSProperty.Overflow OVERFLOW_HIDDEN = CSSProperty.Overflow.HIDDEN;
    public static final CSSProperty.Overflow OVERFLOW_SCROLL = CSSProperty.Overflow.SCROLL;
    public static final CSSProperty.Overflow OVERFLOW_AUTO = CSSProperty.Overflow.AUTO;
    
    public static final CSSProperty.BoxSizing CONTENT_BOX = CSSProperty.BoxSizing.CONTENT_BOX;
    public static final CSSProperty.BoxSizing BORDER_BOX = CSSProperty.BoxSizing.BORDER_BOX;
    
    /** the minimal width of the space between the floating blocks that
     * can be used for placing the in-flow content */
    protected static final float INFLOW_SPACE_THRESHOLD = 15;
    
    /** Does this box contain blocks? */
    protected boolean contblock;
    
    /** Contains any in-flow boxes? */
    protected boolean anyinflow;
    
    /** Floating boxes left */
    protected FloatList fleft;
    
    /** Floating boxes right */
    protected FloatList fright;

    /** If we are a floating box, in which FloatList we are placed. */
    protected FloatList fown;

    /** x coordinate of the content box relative to the content box of the
     * top owner of the FloatList (from left)  */
    protected float floatXl;
    
    /** x coordinate of the content box relative to the content box of the
     * top owner of the FloatList (from right) */
    protected float floatXr;
    
    /** y coordinate of the content box relative to the content box of the 
        top owner of the FloatList */
    protected float floatY;
    
    /** Minimal size of the box. Values of -1 mean 'not set'. */
    protected Dimension min_size;
    
    /** Maximal size of the box. Values of -1 mean 'not set'. */
    protected Dimension max_size;
    
    /** Initialized first line of the box or null when it was not initialized */
    protected LineBox firstLine;
    
    //============================== Width computing ======================
    
    /** true if the box width has been set explicitly */
    protected boolean wset;
    
    /** true if the box height has been set explicitly */
    protected boolean hset;
    
    /** true if the width is relative [%] */
    protected boolean wrelative;
    
    /** true if the left margin is set to auto */
    protected boolean mleftauto;
    
    /** true if the right margin is set to auto */
    protected boolean mrightauto;
    
    /** Width adjustment in pixels. The computed width is adjusted by the given value
     * automatically when updateSizes() is called. This value should be set by setWidthAdjust()
     * and it is reset to zero when loadSizes() is called. */
    protected float widthAdjust;
    
    /** True means that the layout inside has been already computed
     * and the width shouldn't be changed anymore */
    protected boolean widthComputed = false;
    
    /** Originally declared margin. This property saves the original values
     * where the efficient left and right margin may
     * be computed from the containing box */
    protected LengthSet declMargin; 
    
    //============================== Computed style ======================
    
    /** Floating property */
    protected CSSProperty.Float floating;
    
    /** Clearing property */
    protected CSSProperty.Clear clearing;
    
    /** Overflow-X property */
    protected CSSProperty.Overflow overflowX;
        
    /** Overflow-Y property */
    protected CSSProperty.Overflow overflowY;
    
    /** Box-sizing property */
    protected CSSProperty.BoxSizing boxSizing;
        
    /** the left position should be set to static position during the layout */
    protected boolean leftstatic;
    
    /** the top position should be set to static position during the layout */
    protected boolean topstatic;
    
    /** Reference box for absolutely positioned boxes. It is used
     * when some of the absolute coordinates are based on the static position */
    protected Box absReference;
    
    /** Parent box according to DOM. It's used for absolutely positioned boxes
     * where the actual parent box is determined in different way. */
    protected ElementBox domParent;
    
    /** Text-align property */
    protected CSSProperty.TextAlign align;

    /** Text indentation in pixels */
    protected float indent;
    
    /** Clipping region specified using the clip: property (with absolute coordinates) */
    protected TermRect clipRegion;
    
    //=====================================================================
    
    /** Creates a new instance of BlockBox */
    public BlockBox(Element n, VisualContext ctx)
    {
        super(n, ctx);
        
        isblock = true;
        contblock = false;
        anyinflow = false;
        
        fleft = null;
        fright = null;
        fown = null;
        floatXl = 0;
        floatXr = 0;
        floatY = 0;
        firstLine = null;
        
        floating = FLOAT_NONE;
        clearing = CLEAR_NONE;
        overflowX = OVERFLOW_VISIBLE;
        overflowY = OVERFLOW_VISIBLE;
        boxSizing = CONTENT_BOX;
        align = ALIGN_LEFT;
        indent = 0;
        clipRegion = null;
        
        topstatic = false;
        leftstatic = false;
        widthAdjust = 0;
        
      	if (style != null)
      		loadBlockStyle();
    }

    /** Convert an inline box to a block box */
    public BlockBox(InlineBox src)
    {
        super(src.el, src.ctx);
        
        viewport = src.viewport;
        clipblock = src.clipblock;
        isblock = true;
        contblock = false;
        anyinflow = false;
        
        fleft = null;
        fright = null;
        fown = null;
        floatXl = 0;
        floatXr = 0;
        floatY = 0;
        
        floating = FLOAT_NONE;
        clearing = CLEAR_NONE;
        position = src.position;
        overflowX = OVERFLOW_VISIBLE;
        overflowY = OVERFLOW_VISIBLE;
        boxSizing = CONTENT_BOX;
        align = ALIGN_LEFT;
        indent = 0;
        clipRegion = null;

        topset = src.topset;
        leftset = src.leftset;
        bottomset = src.bottomset;
        rightset = src.rightset;
        topstatic = false;
        leftstatic = false;
        widthAdjust = 0;
        
        nested = src.nested;
        startChild = src.startChild;
        endChild = src.endChild;
        
        setStyle(src.getStyle());
    }
    
    public void copyValues(BlockBox src)
    {
        super.copyValues(src);
        contblock = src.contblock;
        anyinflow = src.anyinflow;
        setFloats(src.fleft, src.fright, src.floatXl, src.floatXr, src.floatY);
        fown = src.fown;
        floating = src.floating;
        clearing = src.clearing;
        overflowX = src.overflowX;
        overflowY = src.overflowY;
        boxSizing = src.boxSizing;
        align = src.align;
        indent = src.indent;
        topstatic = src.topstatic;
        leftstatic = src.leftstatic;
        domParent = src.domParent;
        if (src.declMargin != null)
        	declMargin = new LengthSet(src.declMargin);
        clipRegion = src.clipRegion;
    }
    
    @Override
    public BlockBox copyBox()
    {
        BlockBox ret = new BlockBox(el, ctx);
        ret.copyValues(this);
        return ret;
    }

    @Override
    public void initBox()
    {
        setFloats(new FloatList(this), new FloatList(this), 0, 0, 0);
    }

    @Override
    public void addSubBox(Box box)
    {
        super.addSubBox(box);
        if (box.isInFlow())
        {
            anyinflow = true;
            if (box.isBlock())
                contblock = true;
        }
    }

    @Override
    public void setStyle(NodeData s)
    {
    	super.setStyle(s);
    	loadBlockStyle();
    }
    
    @Override
    public String toString()
    {
        return "<" + el.getTagName() + " id=\"" + HTMLNorm.getAttribute(el, "id") + 
               "\" class=\""  + HTMLNorm.getAttribute(el ,"class") + "\">";
    }
    
    //========================================================================
    
    @Override
    public boolean mayContainBlocks()
    {
    	return true;
    }
    
    public boolean containsBlocks()
    {
        return contblock;
    }
    
    public void setFloats(FloatList left, FloatList right, float xl, float xr, float y)
    {
        fleft = left;
        fright = right;
        floatXl = xl;
        floatXr = xr;
        floatY = y;
    }
    
    public void setOwnerFloatList(FloatList list)
    {
    	fown = list;
    }

    public FloatList getOwnerFloatList()
    {
    	return fown;
    }
    
    public CSSProperty.Float getFloating()
    {
        return floating;
    }

    public String getFloatingString()
    {
        return floating.toString();
    }

    public CSSProperty.Clear getClearing()
    {
        return clearing;
    }
    
    public String getClearingString()
    {
        return clearing.toString();
    }
    
    public CSSProperty.Overflow getOverflowX()
    {
        return overflowX;
    }
    
    public CSSProperty.Overflow getOverflowY()
    {
        return overflowY;
    }
    
    public String getOverflowXString()
    {
        return overflowX.toString();
    }
    
    public String getOverflowYString()
    {
        return overflowY.toString();
    }
    
    public float getFloatY()
    {
        return floatY;
    }
    
    /** Returns true if the box is in the normal text flow (not absolutely
     * positioned nor floating) */
    @Override
    public boolean isInFlow()
    {
        return (displayed && floating == FLOAT_NONE && position != POS_ABSOLUTE && position != POS_FIXED);
    }
    
    /** Returns true if the box is absolutely positioned. */
    public boolean isPositioned()
    {
        return (displayed && (position == POS_ABSOLUTE || position == POS_FIXED));
    }
    
    /** Returns true if the box is floating. */
    public boolean isFloating()
    {
        return (displayed && floating != FLOAT_NONE);
    }
    
	@Override
    public boolean containsFlow()
	{
		return anyinflow;
	}
	
    @Override
    public boolean isWhitespace()
    {
        if (anyinflow || encloseFloats())
            return super.isWhitespace();
        else
            return true;
    }

    @Override
	public boolean marginsAdjoin()
	{
    	if (padding.top > 0 || padding.bottom > 0 ||
       		border.top > 0 || border.bottom > 0)
    	{
    		//margins are separated by padding or border
    		return false;
    	}
    	else if (min_size.height > 0)
    	{
    		//when minimal height is not zero, the margins are not adjoining
    		return false;
    	}
    	else if (hset)
    	{
    		//when the height is fixed to zero, margins are adjoining
    		return content.height == 0;
    	}
    	else
    	{
    		//margins can be separated by contents
	        for (int i = startChild; i < endChild; i++)
	        {
	        	Box box = getSubBox(i);
	        	if (box instanceof ElementBox) //all child boxes must have adjoining margins
	        	{
	        		if (!((ElementBox) box).marginsAdjoin())
	        			return false;
	        	}
	        	else
	        	{
	        		if (!box.isWhitespace()) //text boxes must be whitespace
	        			return false;
	        	}
	        }
	        return true;
    	}
	}

    /** Returns true if the element displays at least something */
    @Override
    public boolean affectsDisplay()
    {
        boolean ret = containsFlow();
        //non-zero top or left border
        if (border.top > 0 || border.bottom > 0)
            ret = true;
        //the same with padding
        if (padding.top > 0 || padding.bottom > 0)
            ret = true;
        
        return ret;
    }
	
    @Override
    public boolean canSplitInside()
    {
        return false;
    }

    @Override
    public boolean canSplitBefore()
    {
        return true;
    }

    @Override
    public boolean canSplitAfter()
    {
        return true;
    }

    @Override
    public boolean startsWithWhitespace()
    {
        return false;
    }

    @Override
    public boolean endsWithWhitespace()
    {
        return false;
    }
    
    @Override
    public void setIgnoreInitialWhitespace(boolean b)
    {
    }

    /**
     * Checks if the width of the block may be increase to enclose the children. This is true for special blocks
     * only such as table cells.
     * @return <code>true</code> if the width may be increased
     */
    public boolean canIncreaseWidth()
    {
        return false;
    }
    
    /**
     * Checks whether the block should enclose all the floating children as well.
     * See http://www.w3.org/TR/CSS21/visudet.html#root-height
     * @return <code>true</code> if the floats should enclosed
     */
    protected boolean encloseFloats()
    {
        return overflowX != OVERFLOW_VISIBLE || floating != FLOAT_NONE || position == POS_ABSOLUTE || position == POS_FIXED || display == ElementBox.DISPLAY_INLINE_BLOCK;
    }
    
    /**
     * Checks whether the block bounds may overlap floating element bounds when the block is in flow.
     * See http://www.w3.org/TR/CSS22/visuren.html#bfc-next-to-float
     * @return {@code true} when the block bounds may overlap floating block bounds
     */
    protected boolean mayOverlapFloats()
    {
        return overflowX == OVERFLOW_VISIBLE;
    }
    
    /**
     * Obtains the reference box for absolutely positioned boxes. It is used
     * when some of the absolute coordinates are based on the static position.
     */
    public Box getAbsReference()
    {
        return absReference;
    }
    
    /**
     * Obtains the parent box according to DOM. It's used for absolutely positioned
     * boxes where the actual parent box obtained using {@link Box#getParent()} is 
     * determined in a different way.
     */
    public ElementBox getDomParent()
    {
        return domParent;
    }
    
    /**
     * Sets the width of the content while considering the min- and max- width.
     * @param width the width the set if possible
     */
    public void setContentWidth(float width)
    {
        float w = width;
        if (max_size.width != -1 && w > max_size.width)
            w = max_size.width;
        if (min_size.width != -1 && w < min_size.width)
            w = min_size.width;
        content.width = w;
    }
    
    /**
     * Sets the height of the content while considering the min- and max- height.
     * @param height the height the set if possible
     */
    public void setContentHeight(float height)
    {
        float h = height;
        if (max_size.height != -1 && h > max_size.height)
            h = max_size.height;
        if (min_size.height != -1 && h < min_size.height)
            h = min_size.height;
        content.height = h;
    }
    
    /**
     * Adjusts the content width by the given value. This value is later automatically applied when
     * {@link #updateSizes()} is called and it is reset to zero when {@link #loadSizes()} is called. This function has no
     * effect when the width is explicitly set (i.e. it is not {@code auto}).
     * @param adjust the value to be added to the width
     */
    public void setWidthAdjust(float adjust)
    {
        if (!wset)
            setContentWidth(getContentWidth() - widthAdjust + adjust);
        widthAdjust = adjust;
    }
    
    @Override
    public float totalHeight()
    {
        if (border.top == 0 && border.bottom == 0 &&
            padding.top == 0 && padding.bottom == 0 &&
            content.height == 0) /* no content - margin collapsing applies */
            return Math.max(emargin.top, emargin.bottom);
        else
            return emargin.top + border.top + padding.top + content.height +
                padding.bottom + border.bottom + emargin.bottom;
    }
   
    /**
     * Obtains the first line indentation defined by the text-indent property.
     * @return indentation in pixels
     */
    public float getIndent()
    {
        return indent;
    }
    
    @Override
    public Rectangle getContainingBlock()
    {
        if (position == POS_FIXED)
            return viewport.getVisibleRect();
        else if (position == POS_ABSOLUTE)
            return cbox.getPaddingBounds();
        else
            return super.getContainingBlock();
    }
    
    @Override
    public Rectangle getAbsoluteContainingBlock()
    {
        if (position == POS_FIXED)
            return viewport.getVisibleRect();
        else if (position == POS_ABSOLUTE)
            return cbox.getAbsolutePaddingBounds();
        else
            return super.getAbsoluteContainingBlock();
    }
    
   //========================================================================
    
    /**
     * Moves down all the floating boxes contained in this box and its children. 
     */
    protected void moveFloatsDown(float ofs)
    {
        floatY += ofs;
        for (int i = startChild; i < endChild; i++)
        {
            Box box = getSubBox(i);
            if (box instanceof BlockBox)
            {
                BlockBox block = (BlockBox) box;
                if (block.isInFlow())
                    block.moveFloatsDown(ofs);
                else if (block.getFloating() != BlockBox.FLOAT_NONE)
                    block.moveDown(ofs);
            }
        }
    }
    
    /** 
     * Aligns the subboxes in a line according to the selected alignment settings.
     * @param line The line box to be aligned
     */
    private void alignLineHorizontally(LineBox line, boolean isLast)
    {
        final float dif = content.width - line.getLimits() - line.getWidth(); //difference between maximal available and current width
        if (dif > 0)
        {
            if (align == ALIGN_JUSTIFY)
            {
                if (!isLast)
                    extendInlineChildWidths(dif, line.getStart(), line.getEnd(), true, true);
            }
            else if (align != ALIGN_LEFT)
            {
                for (int i = line.getStart(); i < line.getEnd(); i++) //all inline boxes on this line
                {
                    Box subbox = getSubBox(i);
                    if (subbox instanceof Inline)
                    {
                        if (align == ALIGN_RIGHT)
                            subbox.moveRight(dif);
                        else if (align == ALIGN_CENTER)
                            subbox.moveRight(dif/2);
                    }
                }
            }
        }
    }
    
    private void alignLineVertically(LineBox line)
    {
        for (int i = line.getStart(); i < line.getEnd(); i++) //all inline boxes on this line
        {
            Box subbox = getSubBox(i);
            if (!subbox.isBlock())
            {
                float dif = line.alignBox((Inline) subbox);
                
                //Now, dif is the difference of the content boxes. Recompute to the whole boxes.
                if (subbox instanceof InlineBox)
                    dif = dif - ((ElementBox) subbox).getContentOffsetY(); 

                //Set the  line boxes for positioning the "top" and "bottom" aligned boxes
                if (subbox instanceof InlineElement)
                    ((InlineElement) subbox).setLineBox(line);
                
                //the Y position is used for the boxes that are not "top" or "bottom" aligned
                float y = line.getY() + line.getTopOffset() + (line.getLead() / 2) + dif;
                subbox.moveDown(y);
            }
        }
    }

    /**
     * Computes the efficient sizes of in-flow margins for collapsing
     */
    @Override
    public void computeEfficientMargins()
    {
        //minimal margins
        emargin.top = margin.top;
        emargin.bottom = margin.bottom;
        //check if something inside can be collapsed and it is larger
        if (containsBlocks() && containsFlow())
        {
            BlockBox firstseparated = null;
            float mbottom = 0;
            
            for (int i = 0; i < getSubBoxNumber(); i++)
            {
                BlockBox subbox = (BlockBox) getSubBox(i);
                if (subbox.isDisplayed() && subbox.isInFlow())
                {
                    subbox.computeEfficientMargins();
                    boolean boxempty = subbox.marginsAdjoin();
                    
                    //if no separated box yet, update the top margin
                    if (firstseparated == null && !separatedFromTop(this)) 
                    {
                    	if (subbox.emargin.top > emargin.top)
                    		emargin.top = subbox.emargin.top;
                    }
                    
                    //update the bottom margin
                    if (boxempty)
                    {
                        float m = Math.max(subbox.emargin.top, subbox.emargin.bottom); //margins adjoin: we may use both top or bottom
                    	if (m > mbottom)
                    		mbottom = m;
                    }
                    else
                    	mbottom = subbox.emargin.bottom;
                    
                    if (!boxempty && firstseparated == null)
                        firstseparated = subbox;
                }
            }

            //collapse bottom margins
            if (mbottom > emargin.bottom && !separatedFromBottom(this))
            	emargin.bottom = mbottom;
        }
        
        //if the box is empty, collapse it to a single margin
        if (marginsAdjoin())
        {
        	emargin.top = Math.max(emargin.top, emargin.bottom);
        	emargin.bottom = 0;
        }
        
    }
    
    /** Layout the sub-elements.
     * @param availw Maximal width available to the child elements
     * @param force Use the area even if the used width is greater than maxwidth
     * @param linestart Indicates whether the element is placed at the line start
     * @return <code>true</code> if the box has been succesfully placed
     */
    @Override
    public boolean doLayout(float availw, boolean force, boolean linestart)
    {
    	//if (getElement() != null && getElement().getAttribute("id").equals("gbzc"))
    	//	System.out.println("jo!");
        //Skip if not displayed
        if (!displayed)
        {
            content.setSize(0, 0);
            bounds.setSize(0, 0);
            return true;
        }

        //remove previously splitted children from possible previous layout
        clearSplitted();

        //shrink-to-fit when the width is not given by containing box or specified explicitly
        if (!hasFixedWidth())
        {
            //float min = getMinimalContentWidthLimit();
            float min = Math.max(getMinimalContentWidthLimit(), getMinimalContentWidth());
            float max = getMaximalContentWidth();
            float availcont = availw - emargin.left - border.left - padding.left - emargin.right - border.right - padding.right;
            //float pref = Math.min(max, availcont);
            //if (pref < min) pref = min;
            float pref = Math.min(Math.max(min, availcont), max);
            setContentWidth(pref);
            updateChildSizes();
        }
        
        //the width should be fixed from this point
        widthComputed = true;
        
        /* Always try to use the full width. If the box is not in flow, its width
         * is updated after the layout */
        setAvailableWidth(totalWidth());
        
        if (!contblock)  //block elements containing inline elements only
            layoutInline();
        else //block elements containing block elements
            layoutBlocks();
        
        //allways fits as well possible
        return true;
    }

    /**
     * Lay out inline boxes inside of this block
     */
    protected void layoutInline()
    {
        float x1 = fleft.getWidth(floatY) - floatXl;  //available width with considering floats 
        float x2 = fright.getWidth(floatY) - floatXr;
        if (x1 < 0) x1 = 0;
        if (x2 < 0) x2 = 0;
        float wlimit = getAvailableContentWidth();
        float minx1 = 0 - floatXl;   //maximal available width if there were no floats
        float minx2 = 0 - floatXr;
        if (minx1 < 0) minx1 = 0;
        if (minx2 < 0) minx2 = 0;
        float x = x1; //current x
        float y = 0; //current y
        int lnstr = 0; //the index of the first subbox on current line
        int lastbreak = 0; //last possible position of a line break

        //apply indentation
        x += indent;
        
        //line boxes
        Vector<LineBox> lines = new Vector<LineBox>();
        LineBox curline = firstLine;
        if (curline == null)
            curline = new LineBox(this, 0, 0);
        lines.add(curline);

        for (int i = 0; i < getSubBoxNumber(); i++)
        {
            Box subbox = getSubBox(i);
            
            //if we find a block here, it must be an out-of-flow box
            //make the positioning and continue
            if (subbox.isBlock())
            {
                BlockBox sb = (BlockBox) subbox;
                BlockLayoutStatus stat = new BlockLayoutStatus();
                stat.inlineWidth = x - x1;
                stat.y = y;
                stat.maxh = 0;
                
                boolean atstart = (x <= x1); //check if the line has already started
                
                //clear set - try to find the first possible Y value
                if (sb.getClearing() != CLEAR_NONE)
                {
                    float ny = stat.y;
                    if (sb.getClearing() == CLEAR_LEFT)
                        ny = fleft.getMaxY() - floatY;
                    else if (sb.getClearing() == CLEAR_RIGHT)
                        ny = fright.getMaxY() - floatY;
                    else if (sb.getClearing() == CLEAR_BOTH)
                        ny = Math.max(fleft.getMaxY(), fright.getMaxY()) - floatY;
                    if (stat.y < ny) stat.y = ny;
                }
                
                if (sb.getFloating() == FLOAT_LEFT || sb.getFloating() == FLOAT_RIGHT) //floating boxes
                {
                    layoutBlockFloating(sb, wlimit, stat);
                    //if there were some boxes before the float on the line, move them behind
                    if (sb.getFloating() == FLOAT_LEFT && stat.inlineWidth > 0 && curline.getStart() < i)
                    {
                        for (int j = curline.getStart(); j < i; j++)
                        {
                            Box child = getSubBox(j);
                            if (!child.isBlock())
                                child.moveRight(sb.getWidth());
                        }
                        x += sb.getWidth();
                    }
                }
                else //absolute or fixed positioning
                {
                    layoutBlockPositioned(sb, stat);
                }
                
                //in case the block was floating, we need to update the bounds
                x1 = fleft.getWidth(y + floatY) - floatXl;
                x2 = fright.getWidth(y + floatY) - floatXr;
                if (x1 < 0) x1 = 0;
                if (x2 < 0) x2 = 0;
                //if the line hasn't started yet, update its start
                if (atstart && x < x1)
                    x = x1;
                //continue with next subboxes
                continue;
            }
            
            //process inline elements
            if (subbox.canSplitBefore())
                lastbreak = i;
            boolean split;
            do //repeat while the box is being split to sub-boxes
            {
                split = false;
                float space = wlimit - x1 - x2; //total space on the line
                boolean narrowed = (x1 > minx1 || x2 > minx2); //the space is narrowed by floats and it may be enough space somewhere below
                //force: we're at the leftmost position or the line cannot be broken
                // if there is no space on the line because of the floats, do not force
                boolean f = (x == x1 || lastbreak == lnstr || !allowsWrapping()) && !narrowed;
                //do the layout                
                boolean fit = false;
                if (space >= INFLOW_SPACE_THRESHOLD || !narrowed)
                    fit = subbox.doLayout(wlimit - x - x2, f, x == x1);
                if (fit) //positioning succeeded, at least a part fit -- set the x coordinate
                {
                    if (subbox.isInFlow())
                    {
                        subbox.setPosition(x,  0); //y position will be determined during the line box vertical alignment
                        x += subbox.getWidth();
                    }
                    //update current line metrics
                    curline.considerBox((Inline) subbox);
                }
                
                //check line overflows
                boolean over = (x > wlimit - x2); //space overflow?
                boolean linebreak = (subbox instanceof Inline && ((Inline) subbox).finishedByLineBreak()); //finished by a line break?
                if (!fit && narrowed && (x == x1 || lastbreak == lnstr)) //failed because of no space caused by floats
                {
                    //finish the line if there are already some boxes on the line
                    if (lnstr < i)
                    {
                        lnstr = i; //new line starts here
                        curline.setEnd(lnstr); //finish the old line
                        curline = new LineBox(this, lnstr, y); //create the new line
                        lines.add(curline);
                    }
                    //go to the new line
                    y += getLineHeight();
                    curline.setY(y);
                    x1 = fleft.getWidth(y + floatY) - floatXl;
                    x2 = fright.getWidth(y + floatY) - floatXr;
                    if (x1 < 0) x1 = 0;
                    if (x2 < 0) x2 = 0;
                    x = x1;
                    //force repeating the same once again unless line height is non-positive (prevent infinite loop)
                    if (getLineHeight() > 0)
                        split = true;
                }
                else if ((!fit && lastbreak > lnstr) //line overflow and the line can be broken
                           || (fit && (over || linebreak || subbox.getRest() != null))) //or something fit but something has left
                {
                    //the width and height for text alignment
                    curline.setWidth(x - x1);
                    curline.setLimits(x1, x2);
                    //go to the new line
                    y += curline.getMaxBoxHeight();
                    x1 = fleft.getWidth(y + floatY) - floatXl;
                    x2 = fright.getWidth(y + floatY) - floatXr;
                    if (x1 < 0) x1 = 0;
                    if (x2 < 0) x2 = 0;
                    x = x1;

                    //create a new line
                    if (!fit) //not fit - try again with a new line
                    {
                        lnstr = i; //new line starts here
                        curline.setEnd(lnstr); //finish the old line
                        curline = new LineBox(this, lnstr, y); //create the new line
                        lines.add(curline);
                        split = true; //force repeating the same once again
                    }
                    else if (over || linebreak || subbox.getRest() != null) //something fit but not everything placed or line exceeded - create a new empty line
                    {
                        if (subbox.getRest() != null)
                            insertSubBox(i+1, subbox.getRest()); //insert a new subbox with the rest
                        lnstr = i+1; //new line starts with the next subbox
                        curline.setEnd(lnstr); //finish the old line
                        curline = new LineBox(this, lnstr, y); //create the new line
                        lines.add(curline);
                    }
                }
            } while (split);
            
            if (subbox.canSplitAfter())
            	lastbreak = i+1;
       }
        
        //block height
        if (!hasFixedHeight())
        {
                y += curline.getMaxBoxHeight(); //last unfinished line
                if (encloseFloats())
                {
                    //enclose all floating boxes we own
                    float mfy = getFloatHeight() - floatY;
                    if (mfy > y) y = mfy;
                }
                //the total height is the last Y coordinate
                setContentHeight(y);
                updateSizes();
                updateChildSizes();
        }
        setSize(totalWidth(), totalHeight());
        
        //finish the last line
        curline.setWidth(x - x1); 
        curline.setLimits(x1, x2);
        curline.setEnd(getSubBoxNumber());
        //align the lines according to the real box width
        for (Iterator<LineBox> it = lines.iterator(); it.hasNext();)
        {
            LineBox line = it.next();
            alignLineHorizontally(line, !it.hasNext());
            alignLineVertically(line);
        }
    }

    /**
     * Lay out nested block boxes in this box
     */
    protected void layoutBlocks()
    {
        float wlimit = getAvailableContentWidth();
        BlockLayoutStatus stat = new BlockLayoutStatus();
        float mtop = 0; //current accumulated top margin
        float mbottom = 0; //current accumulated bottom marin

        for (int i = 0; i < getSubBoxNumber(); i++)
        {
            float nexty = stat.y; //y coordinate after positioning the subbox 
            BlockBox subbox = (BlockBox) getSubBox(i);
            
            if (subbox.isDisplayed())
            {
            	boolean clearance = false; //clearance applied?
            	
                //clear set - try to find the first possible Y value
                if (subbox.getClearing() != CLEAR_NONE)
                {
                    float ny = stat.y;
                    if (subbox.getClearing() == CLEAR_LEFT)
                        ny = fleft.getMaxY() - floatY;
                    else if (subbox.getClearing() == CLEAR_RIGHT)
                        ny = fright.getMaxY() - floatY;
                    else if (subbox.getClearing() == CLEAR_BOTH)
                        ny = Math.max(fleft.getMaxY(), fright.getMaxY()) - floatY;
                    if (stat.y < ny) 
                    {
                    	stat.y = ny;
                    	clearance = true;
                    }
                }
                
                if (subbox.isInFlow()) //normal flow
                {
                    boolean boxempty = subbox.marginsAdjoin(); 
                    
                	//the border edge of the parent or the last placed box
                	float borderY = stat.y;
                	if (stat.lastinflow != null)
                	    borderY -= stat.lastinflow.emargin.bottom; //do not consider the margin applied by the layout
                	
                	//update expected top margin
                	if (subbox.emargin.top > mtop)
                	    mtop = subbox.emargin.top;
                	
                    //top margins are separated?
			        if (stat.firstseparated == null && separatedFromTop(this))
			        {
			        	//separated - cannot collapse, use the largest margin
			            borderY += mtop;
			        }
			        
			        //subsequent block margins
			        if (stat.firstseparated != null)
			        {
    		        	if (clearance) //some clearance, cannot collapse
    		        		borderY += mtop + mbottom;
    		        	else //do collapse
    		        		borderY += collapsedMarginHeight(mtop, mbottom);
			        }
			        
			        stat.lastinflow = subbox;
                    if (!boxempty && stat.firstseparated == null)
                        stat.firstseparated = subbox;
                    if (!boxempty)
                    {
                        stat.lastseparated = subbox;
                        mtop = 0;
                        mbottom = subbox.emargin.bottom;
                    }
			        
                    //update expected bottom margin
                    if (stat.lastseparated != null) //compute maximum of bottom margins after some separation
                    {
                        if (subbox.emargin.bottom > mbottom)
                            mbottom = subbox.emargin.bottom;
                    }
                    
                    if (subbox.emargin.top > 0) //place the border edge appropriately: overlap positive margins
                        stat.y = borderY - subbox.emargin.top;
                    
                    if (subbox.mayOverlapFloats())
                        layoutBlockInFlow(subbox, wlimit, stat);
                    else
                        layoutBlockInFlowAvoidFloats(subbox, wlimit, stat);
                        
                    if (subbox.getRest() != null) //not everything placed -- insert the rest to the queue
                        insertSubBox(i+1, subbox.getRest());
                    nexty = stat.y; //the flow influences current y
                }
                else if (subbox.getFloating() == FLOAT_LEFT || subbox.getFloating() == FLOAT_RIGHT) //floating boxes
                {
                    layoutBlockFloating(subbox, wlimit, stat);
                }
                else //absolute or fixed positioning
                {
                    layoutBlockPositioned(subbox, stat);
                }
                //accept the resulting Y coordinate
                stat.y = nexty;
            }
        }

        //collapse bottom margins
        if (!separatedFromBottom(this))
        {
  			stat.y -= mbottom;
        }
        
        //update the height when not set or set to "auto"
        if (!hasFixedHeight())
        {
            if (encloseFloats())
            {
                //enclose all floating boxes we own
                // http://www.w3.org/TR/CSS21/visudet.html#root-height
                float mfy = getFloatHeight() - floatY;
                if (mfy > stat.y) stat.y = mfy;
            }
            //the total height is the last Y coordinate
            setContentHeight(stat.y);
            updateSizes();
            updateChildSizes();
        }
        setSize(totalWidth(), totalHeight());
    }

    protected void layoutBlockInFlow(BlockBox subbox, float wlimit, BlockLayoutStatus stat)
    {
        //new floating box limits
        float newfloatXl = floatXl + subbox.margin.left
                            + subbox.border.left + subbox.padding.left;
        float newfloatXr = floatXr + subbox.margin.right
                            + subbox.border.right + subbox.padding.right;
        float newfloatY = floatY + subbox.emargin.top
                            + subbox.border.top + subbox.padding.top;
        //consider the relative positioning if necessary
        if (subbox.position == POS_RELATIVE)
        {
            float dx = subbox.leftset ? subbox.coords.left : (-subbox.coords.right);
            float dy = subbox.topset ? subbox.coords.top : (-subbox.coords.bottom);
            newfloatXl += dx;
            newfloatXr -= dx;
            newfloatY += dy;
        }
        
        //floats should not exceed their parent box
        if (newfloatXl < 0) newfloatXl = 0;
        if (newfloatXr < 0) newfloatXr = 0;
        //position the box
        subbox.setFloats(fleft, fright, newfloatXl, newfloatXr, stat.y + newfloatY);
        subbox.setPosition(0,  stat.y);
        subbox.doLayout(wlimit, true, true);
        stat.y += subbox.getHeight();
        //maximal width
        if (subbox.getWidth() > stat.maxw)
            stat.maxw = subbox.getWidth();
    }
    
    // http://www.w3.org/TR/CSS22/visuren.html#bfc-next-to-float 
    protected void layoutBlockInFlowAvoidFloats(BlockBox subbox, float wlimit, BlockLayoutStatus stat)
    {
        final float minw = subbox.getMinimalDecorationWidth(); //minimal subbox width for computing the space -- content is not considered (based on other browser observations) 
        float yoffset = stat.y + floatY; //starting offset
        float availw = 0;
        do
        {
            float fy = yoffset;
            float flx = fleft.getWidth(fy) - floatXl;
            if (flx < 0) flx = 0;
            float frx = fright.getWidth(fy) - floatXr;
            if (frx < 0) frx = 0;
            float avail = wlimit - flx - frx;
            
            //if it does not fit the width, try to move down
            //TODO the available space must be tested for the whole height of the subbox
            final float startfy = fy;
            //System.out.println("minw=" + minw + " avail=" + avail + " availw=" + availw);
            while ((flx > floatXl || frx > floatXr) //if the space can be narrower at least at one side
                   && (minw > avail)) //the subbox doesn't fit in this Y coordinate
            {
                float nexty = FloatList.getNextY(fleft, fright, fy);
                if (nexty == -1)
                    fy += Math.max(stat.maxh, getLineHeight()); //if we don't know try increasing by a line
                else
                    fy = nexty;
                //recompute the limits for the new fy
                flx = fleft.getWidth(fy) - floatXl;
                if (flx < 0) flx = 0;
                frx = fright.getWidth(fy) - floatXr;
                if (frx < 0) frx = 0;
                avail = wlimit - flx - frx;
            }
            //do not consider the top margin when moving down
            if (fy > startfy && subbox.margin.top != 0)
            {
                fy -= subbox.margin.top;
                if (fy < startfy) fy = startfy;
            }
            stat.y = fy - floatY;
            
            //position the box
            subbox.setFloats(new FloatList(subbox), new FloatList(subbox), 0, 0, 0);
            subbox.setPosition(flx,  stat.y);
            subbox.setWidthAdjust(-flx - frx);
            //if (availw != 0)
            //    System.out.println("jo!");
            subbox.doLayout(avail, true, true);
            //System.out.println("H=" + subbox.getHeight());
            
            //check the colisions after the layout
            float xlimit[] = computeFloatLimits(fy, fy + subbox.getBounds().height, new float[]{flx, frx});
            availw = wlimit - xlimit[0] - xlimit[1];
            if (minw > availw) //the whole box still does not fit
                yoffset = FloatList.getNextY(fleft, fright, fy); //new starting Y 
        } while (minw > availw && yoffset != -1);
        
        stat.y += subbox.getHeight();
        //maximal width
        if (subbox.getWidth() > stat.maxw)
            stat.maxw = subbox.getWidth();
    }
    
    /**
     * Calculates the position for a floating box in the given context.
     * @param subbox the box to be placed
     * @param wlimit the width limit for placing all the boxes
     * @param stat status of the layout that should be updated
     */
    protected void layoutBlockFloating(BlockBox subbox, float wlimit, BlockLayoutStatus stat)
    {
        subbox.setFloats(new FloatList(subbox), new FloatList(subbox), 0, 0, 0);
        subbox.doLayout(wlimit, true, true);
        FloatList f = (subbox.getFloating() == FLOAT_LEFT) ? fleft : fright;    //float list at my side
        FloatList of = (subbox.getFloating() == FLOAT_LEFT) ? fright : fleft;   //float list at the opposite side
        float floatX = (subbox.getFloating() == FLOAT_LEFT) ? floatXl : floatXr;  //float offset at this side
        float oFloatX = (subbox.getFloating() == FLOAT_LEFT) ? floatXr : floatXl; //float offset at the opposite side
        
        float fy = stat.y + floatY;  //float Y position
        if (fy < f.getLastY()) fy = f.getLastY(); //don't place above the last placed box

        float fx = f.getWidth(fy);   //total width of floats at this side
        if (fx < floatX) fx = floatX; //stay in the containing box if it is narrower
        if (fx == 0 && floatX < 0) fx = floatX; //if it is wider (and there are no floating boxes yet)

        float ofx = of.getWidth(fy); //total width of floats at the opposite side
        if (ofx < oFloatX) ofx = oFloatX; //stay in the containing box at the opposite side
        if (ofx == 0 && oFloatX < 0) ofx = oFloatX;

        //moving the floating box down until it fits
        while ((fx > floatX || ofx > oFloatX || stat.inlineWidth > 0) //if the space can be narrower at least at one side
               && (stat.inlineWidth + fx - floatX + ofx - oFloatX + subbox.getWidth() > wlimit)) //the subbox doesn't fit in this Y coordinate
        {
            float nexty = FloatList.getNextY(fleft, fright, fy);
            if (nexty == -1)
                fy += Math.max(stat.maxh, getLineHeight()); //if we don't know try increasing by a line
            else
                fy = nexty;
            //recompute the limits for the new fy
            fx = f.getWidth(fy);
            if (fx < floatX) fx = floatX;
            if (fx == 0 && floatX < 0) fx = floatX;
            ofx = of.getWidth(fy);
            if (ofx < oFloatX) ofx = oFloatX;
            if (ofx == 0 && oFloatX < 0) ofx = oFloatX;
            //do not consider current line below
            stat.inlineWidth = 0;
        }

        subbox.setPosition(fx, fy);
        f.add(subbox);
        //a floating box must enclose all the floats inside
        float floatw = maxFloatWidth(fy, fy + subbox.getHeight());
        //maximal width
        if (floatw > stat.maxw) stat.maxw = floatw;
        if (stat.maxw > wlimit) stat.maxw = wlimit;
    }
    
    protected void layoutBlockPositioned(BlockBox subbox, BlockLayoutStatus stat)
    {
        //calculate the available width for positioned boxes
        float wlimit = availwidth;
        if (leftset) wlimit -= coords.left;
        if (rightset) wlimit -= coords.right;
        //layout the contents
        subbox.setFloats(new FloatList(subbox), new FloatList(subbox), 0, 0, 0);
        subbox.doLayout(wlimit, true, true);
    }

    /**
     * Initializes the first line box with the box properties. This may be used for considering special content
     * such as list item markers.
     * @param box the box that should be used for the initialization
     */
    public void initFirstLine(ElementBox box)
    {
        if (firstLine == null)
            firstLine = new LineBox(this, 0, 0);
        firstLine.considerBoxProperties(box);
        //recursively apply to the first in-flow box, when it is a block box
        for (int i = startChild; i < endChild; i++)
        {
            Box child = getSubBox(i);
            if (child.isInFlow())
            {
                if (child.isBlock())
                    ((BlockBox) child).initFirstLine(box);
                break;
            }
        }
    }
    
    @Override
    public void absolutePositions()
    {
        updateStackingContexts();
        if (displayed)
        {
            //my top left corner
            final Rectangle cblock = getAbsoluteContainingBlock();
            float x = cblock.x + bounds.x;
            float y = cblock.y + bounds.y;

            if (floating == FLOAT_NONE)
            {
                if (position == POS_RELATIVE)
                {
                    x += leftset ? coords.left : (-coords.right);
                    y += topset ? coords.top : (-coords.bottom);
                }
                else if (position == POS_ABSOLUTE || position == POS_FIXED)
                {
                    if (topstatic || leftstatic)
                    {
                        updateStaticPosition();
                    }
                    x = cblock.x + coords.left;
                    y = cblock.y + coords.top;
                    //if fixed, update the position by the viewport visible offset
                    if (position == POS_FIXED && getContainingBlockBox() instanceof Viewport)
                    {
                        x += ((Viewport) getContainingBlockBox()).getVisibleRect().x; 
                        y += ((Viewport) getContainingBlockBox()).getVisibleRect().y; 
                    }
                }
            }
            else if (floating == FLOAT_LEFT)
            {
            	BlockBox listowner = fown.getOwner();
                x = listowner.getAbsoluteContentX() + bounds.x;
                y = listowner.getAbsoluteContentY() + bounds.y;
            }
            else if (floating == FLOAT_RIGHT)
            {
            	BlockBox listowner = fown.getOwner();
                x = listowner.getAbsoluteContentX() + listowner.getContentWidth() - bounds.width - bounds.x;
                y = listowner.getAbsoluteContentY() + bounds.y;
            }

            //set the absolute coordinates
            absbounds.x = x;
            absbounds.y = y;
            
            //update the width and height according to overflow of the cblock
            absbounds.width = bounds.width;
            absbounds.height = bounds.height;
            
            if (isDisplayed())
            {
                if (isVisible())
                {
                    if (clipblock == viewport)
                        viewport.updateBoundsFor(absbounds);
                    else
                        viewport.updateBoundsFor(getClippedBounds());
                }
                
                //repeat for all valid subboxes
                for (int i = startChild; i < endChild; i++)
                    getSubBox(i).absolutePositions();
            }
            
        }
    }
    
    /** Obtains the static position of an absolutely positioned box
     * from the reference box (if any) */
    private void updateStaticPosition()
    {
        if (topstatic || leftstatic)
        {
            ElementBox cblock = getContainingBlockBox();
            if (absReference != null)
            {
                //compute the bounds of the reference box relatively to our containing block
                final Rectangle cb = getContainingBlockBox().getAbsoluteBounds();
                //position relatively to the border edge
                if (topstatic)
                {
                    Rectangle ab = new Rectangle(absReference.getAbsoluteBounds());
                    ab.x = ab.x - cb.x;
                    ab.y = ab.y - cb.y;
                    if (!absReference.isblock || ((BlockBox) absReference).getFloating() == FLOAT_NONE) //not-floating boxes: place below
                        coords.top = ab.y + ab.height - cblock.emargin.top - cblock.border.top;
                    else //floating blocks: place top-aligned
                        coords.top = ab.y - cblock.emargin.top - cblock.border.top;
                }
                if (leftstatic)
                {
                    final ElementBox refcblock = absReference.getContainingBlockBox();
                    if (refcblock != null)
                        coords.left = refcblock.getAbsoluteContentBounds().x - cb.x - cblock.emargin.left - cblock.border.left;
                    else
                        coords.left = 0;
                }
                //the reference box position may be computed later: require recomputing
                viewport.requireRecomputePositions();
            }
            else if (domParent != null) //no reference box, we are probably the first box in our parent
            {
                //find the nearest DOM parent that is part of our box tree
                ElementBox dparent = domParent; 
                while (dparent != null && dparent.getContainingBlockBox() == null)
                    dparent = dparent.getParent();
                //compute the bounds of the reference box relatively to our containing block
                Rectangle ab = new Rectangle(dparent.getAbsoluteContentBounds());
                Rectangle cb = cblock.getAbsoluteBounds();
                ab.x = ab.x - cb.x;
                ab.y = ab.y - cb.y;
                //position relatively to the border edge
                if (topstatic)
                {
                    coords.top = ab.y - cblock.emargin.top - cblock.border.top;
                }
                if (leftstatic)
                {
                    coords.left = ab.x - cblock.emargin.left - cblock.border.left;
                }
                //the reference box position may be computed later: require recomputing
                viewport.requireRecomputePositions();
            }
            else //nothing available, this should not happen
            {
                log.warn("No static position available for " + this.toString());
                //use containing block as a fallback
                if (topstatic)
                    coords.top = cblock.padding.top;
                if (leftstatic)
                    coords.left = cblock.padding.left;
            }
        }
    }

    @Override
    public float getAvailableContentWidth()
    {
        float ret = availwidth - margin.left - border.left - padding.left 
                  - padding.right - border.right - margin.right;
        if (max_size.width != -1 && ret > max_size.width)
            ret = max_size.width;
        return ret;
    }
    
    @Override
    public float getMinimalWidth()
    {
        float ret = 0;
        //if the width is set or known implicitely, return the width
        if (wset && !wrelative)
            ret = content.width;
        //return the maximum of the nested minimal widths
        else
            ret = getMinimalContentWidth();
        //check against the maximal and minimal widths
        if (max_size.width != -1 && ret > max_size.width)
            ret = max_size.width;
        if (min_size.width != -1 && ret < min_size.width)
            ret = min_size.width;
        //increase by margin, padding, border
        ret += declMargin.left + padding.left + border.left +
               declMargin.right + padding.right + border.right;
        return ret;
    }

    /**
     * Computes the minimal width of the box content from the contained sub-boxes.
     * @return the minimal content width
     */
    protected float getMinimalContentWidth()
    {
        float ret = 0;
        float max = 0; //block children
        float sum = 0; //inline children
        for (int i = startChild; i < endChild; i++)
        {
            Box box = getSubBox(i);
            if (box instanceof Inline)
            {
                if (allowsWrapping() && box.canSplitBefore())
                    sum = 0;
                sum += box.getMinimalWidth();
            }
            else
            {
                BlockBox block = (BlockBox) box;
                if (block.position != POS_ABSOLUTE && block.position != POS_FIXED) //absolute or fixed position boxes don't affect the width
                {
                    float w = box.getMinimalWidth();
                    if (w > max) max = w;
                    sum = 0;
                }
            }
            
            if (sum > ret) ret = sum;
            if (max > ret) ret = max;
            
            if (allowsWrapping() && box.canSplitAfter())
                sum = 0;
        }
        return ret;
    }

    /**
     * Get the minimal width of the margins, borders and paddings. The box content width
     * is used only when it is explicitly specified.
     * @return
     */
    protected float getMinimalDecorationWidth()
    {
        float ret = 0;
        //if the width is set or known implicitely, return the width
        if (wset)
            ret = content.width;
        //check against the maximal and minimal widths
        if (max_size.width != -1 && ret > max_size.width)
            ret = max_size.width;
        if (min_size.width != -1 && ret < min_size.width)
            ret = min_size.width;
        //increase by margin, padding, border
        ret += declMargin.left + padding.left + border.left +
               declMargin.right + padding.right + border.right;
        return ret;
    }
    
    @Override
    public float getMaximalWidth()
    {
        float ret;
        //if the width is set or known implicitely, return the width
        if (wset && !wrelative)
            ret = content.width;
        else
            ret = getMaximalContentWidth();
        //check against the maximal and minimal widths
        if (max_size.width != -1 && ret > max_size.width)
            ret = max_size.width;
        if (min_size.width != -1 && ret < min_size.width)
            ret = min_size.width;
        //increase by margin, padding, border
        ret += declMargin.left + padding.left + border.left +
               declMargin.right + padding.right + border.right;
        return ret;
    }

    /**
     * Computes the maximal width of the box content from the contained sub-boxes.
     * @return the maximal content width
     */
    protected float getMaximalContentWidth()
    {
        float sum = 0;
        float max = 0;
        //the inline elements inside are summed up on the line
        //the floating boxes are placed side by side
        //the maximum of the remaining block boxes is taken 
        for (int i = startChild; i < endChild; i++)
        {
            Box subbox = getSubBox(i);
            if (subbox.isBlock()) //block boxes
            {
                BlockBox block = (BlockBox) subbox;
                if (block.getFloating() != BlockBox.FLOAT_NONE) //floating block
                {
                    sum += subbox.getMaximalWidth();
                }
                else if (!block.isInFlow()) //positioned blocks
                {
                	//positioned blocks should not be taken into account
                }
                else //in-flow blocks
                {
                    float sm = subbox.getMaximalWidth();
                    if (sm > max) max = sm;
                    if (sum > max) max = sum;
                    sum = 0; //end of line forced by this block
                }
            }
            else //inline boxes
            {
                if (preservesLineBreaks())
                {
                    float sm = subbox.getMaximalWidth();
                    if (sm > max) max = sm;
                }
                else
                    sum += subbox.getMaximalWidth();
            }
        }
        return Math.max(sum, max);
    }
    
    /**
     * Determines the minimal width as it is limited by the ancestors (for in-flow boxes)
     * or by the explicit setting of <code>width</code> or <code>min-width</code> properties.
     * @return the minimal width
     */ 
    public float getMinimalContentWidthLimit()
    {
    	float ret;
    	float dif = declMargin.left + padding.left + border.left +
               	  declMargin.right + padding.right + border.right;
    	if (wset)
    		ret = content.width;
    	else if (min_size.width != -1)
    		ret = min_size.width;
    	else if (isInFlow())
    		ret = ((BlockBox) getContainingBlockBox()).getMinimalContentWidthLimit() - dif;
    	else
    		ret = 0;
    		
    	return ret;
    }
    
    /**
     * Recursively finds the baseline of the first in-flow inline box.
     * @return The baseline offset in the element content or -1 if there are no in-flow boxes.
     */
    public float getFirstInlineBoxBaseline()
    {
        return recursiveGetFirstInlineBoxBaseline(this);
    }
    
    protected float recursiveGetFirstInlineBoxBaseline(ElementBox root)
    {
        //find the first in-flow box
        Box box = null;
        for (int i = root.startChild; i < root.endChild; i++)
        {
            box = root.getSubBox(i);
            if (box.isInFlow())
                break;
            else
                box = null;
        }
        
        if (box != null)
        {
            if (box instanceof Inline)
                return box.getContentY() + ((Inline) box).getBaselineOffset();
            else
                return box.getContentY() + recursiveGetFirstInlineBoxBaseline((ElementBox) box);
        }
        else
            return -1; //no inline box found
    }
    
    /**
     * @return true if the width is specified relatively
     */
    public boolean isRelative()
    {
        return wrelative;
    }

	@Override
	public boolean hasFixedWidth()
	{
	    return wset //width set explicitly 
	            || isInFlow() //in flow boxes fixed to their containing block
	            || (isPositioned() && !mleftauto && !mrightauto && leftset && rightset); //positioned blocks with auto width but fixed on both sides 
	}

	@Override
	public boolean hasFixedHeight()
	{
		return hset; //only true if the height is set explicitly
	}

    /**
     * Computes the maximal height of the floating boxes inside of this box and
     * all its children.
     * @return the total height of the floating boxes
     */
    public float getFloatHeight()
    {
        if (fleft != null && fright != null)
        {
            float mfy = Math.max(fleft.getMaxYForOwner(this, true), fright.getMaxYForOwner(this, true));
            if (this.containsBlocks())
            {
                for (int i = 0; i < getSubBoxNumber(); i++)
                {
                    Box subbox = getSubBox(i);
                    if (subbox instanceof BlockBox && !((BlockBox) subbox).isPositioned()) //do not consider positioned boxes
                    {
                        float cmfy = ((BlockBox) subbox).getFloatHeight();
                        if (cmfy > mfy) mfy = cmfy;
                    }
                }
            }
            return mfy;
        }
        else
            return 0;
    }
    
    @Override
    public void draw(DrawStage turn)
    {
        if (isDisplayed() && isDeclaredVisible())
        {
            if (!this.formsStackingContext())
            {
                switch (turn)
                {
                    case DRAW_NONINLINE:
                        if (floating == FLOAT_NONE)
                        {
                            if (isVisible())
                                getViewport().getRenderer().renderElementBackground(this);
                            drawChildren(turn);
                        }
                        break;
                    case DRAW_FLOAT:
                        if (floating != FLOAT_NONE)
                        {
                            if (isVisible())
                                getViewport().getRenderer().renderElementBackground(this);
                            drawStackingContext(true);
                        }
                        else
                            drawChildren(turn);
                        break;
                    case DRAW_INLINE:
                        //do nothing but check the children
                        if (floating == FLOAT_NONE)
                        {
                            getViewport().getRenderer().startElementContents(this);
                            drawChildren(turn);
                            getViewport().getRenderer().finishElementContents(this);
                        }
                        break;
                }
            }
        }
    }
    
    @Override
    public Rectangle getClippedBounds()
    {
        //absolutely positioned blocks may have clipping specified
        Rectangle cr = getClippingRectangle();
        if (cr == null)
            return super.getClippedBounds();
        else
            return super.getClippedBounds().intersection(cr);
    }

    @Override
    public Rectangle getClippedContentBounds()
    {
        //absolutely positioned blocks may have clipping specified
        Rectangle cr = getClippingRectangle();
        if (cr == null)
            return super.getClippedContentBounds();
        else
            return super.getClippedContentBounds().intersection(cr);
    }

    /**
     * Computes the absolute coordinates of the clipping rectangle specified using the <code>clip:</code> property.
     * @return The absolute coordinates of the clipping rectangle or <code>null</code> when no clipping is applied.
     */
    public Rectangle getClippingRectangle()
    {
        if (clipRegion != null)
        {
            List<TermLength> args = clipRegion.getValue();
            CSSDecoder dec = new CSSDecoder(ctx);
            Rectangle brd = getAbsoluteBorderBounds();
            float x1 = brd.x;
            float y1 = brd.y;
            float x2 = brd.x + brd.width - 1;
            float y2 = brd.y + brd.height - 1;
            TermLength top = args.get(0);
            TermLength right = args.get(1);
            TermLength bottom = args.get(2);
            TermLength left = args.get(3);
            
            if (left != null)
                x1 = brd.x + dec.getLength((TermLength) left, false, 0, 0, 0);
            if (top != null)
                y1 = brd.y + dec.getLength((TermLength) top, false, 0, 0, 0);
            if (right != null)
                x2 = brd.x + dec.getLength((TermLength) right, false, 0, 0, 0);
            if (bottom != null)
                y2 = brd.y + dec.getLength((TermLength) bottom, false, 0, 0, 0);
            
            return new Rectangle(x1, y1, x2 - x1, y2 - y1);
        }
        else
            return null;
    }
    
    //=======================================================================
    
    protected void loadBlockStyle()
    {
        floating = style.getProperty("float");
        if (floating == null) floating = FLOAT_NONE;

        clearing = style.getProperty("clear");
        if (clearing == null) clearing = CLEAR_NONE;
        
        overflowX = style.getProperty("overflow-x");
        if (overflowX == null) overflowX = OVERFLOW_VISIBLE;
        overflowY = style.getProperty("overflow-y");
        if (overflowY == null) overflowY = OVERFLOW_VISIBLE;
        //overflow: visible should compute to auto if the other one is not visible
        if (overflowX == OVERFLOW_VISIBLE && overflowY != OVERFLOW_VISIBLE)
            overflowX = OVERFLOW_AUTO;
        else if (overflowY == OVERFLOW_VISIBLE && overflowX != OVERFLOW_VISIBLE)
            overflowY = OVERFLOW_AUTO;
        
        boxSizing = style.getProperty("box-sizing");
        if (boxSizing == null) boxSizing = CONTENT_BOX;
        
        align = style.getProperty("text-align");
        if (align == null) align = ALIGN_LEFT;
        
        //apply combination rules
        //http://www.w3.org/TR/CSS21/visuren.html#dis-pos-flo
        if (display == ElementBox.DISPLAY_NONE)
        {
            position = POS_STATIC;
            floating = FLOAT_NONE;
        }
        else if (position == POS_ABSOLUTE || position == POS_FIXED)
        {
            floating = FLOAT_NONE;
        }
        
        //absolutely positioned elements may have clipping
        if (position == POS_ABSOLUTE)
        {
            CSSProperty.Clip clip = style.getProperty("clip");
            if (clip == Clip.shape)
                clipRegion = style.getValue(TermRect.class, "clip");
        }
    }
    
    /** Compute the total width of a block element according to the min-, max-,
     * width properties */
    protected float blockWidth()
    {
    	return content.width;
    }

    /** Compute the height of a block element according to the min-, max-,
     * height properties */
    protected float blockHeight()
    {
    	return content.height;
    }

    /** 
     * Returns maximal width of floats (left and right together) in 
     * a specified Y interval in this block.
     * @param y1 the starting Y value
     * @param y2 the end Y value
     * @return the maximal sum of the left and the right float width
     */
    protected float maxFloatWidth(float y1, float y2)
    {
        float ret = 0;
        float fy = y1;
        while (fy < y2)
        {
            float w = fleft.getWidth(fy) + fright.getWidth(fy);
            if (w > ret)
                ret = w;
            
            float nexty = FloatList.getNextY(fleft, fright, fy);
            if (nexty != -1)
                fy = nexty;
            else
                break;
        }
        return ret;
    }

    /**
     * Computes the maximal widths of floats on both left and right for the given range of y coordinate.
     * @param y1 starting y coordinate
     * @param y2 ending y coordinate
     * @param fx starting values of [left, right] width (for y1) 
     * @return updated fx[] containging [maxleft, maxright] coordinates.
     */
    protected float[] computeFloatLimits(float y1, float y2, float[] fx)
    {
        float fy = y1;
        while (fy < y2)
        {
            float nexty = FloatList.getNextY(fleft, fright, fy);
            if (nexty != -1)
                fy = nexty;
            else
                break;
            // recompute the limits for the new fy
            if (fy < y2)
            {
                float flx = fleft.getWidth(fy) - floatXl;
                if (flx < 0) flx = 0;
                float frx = fright.getWidth(fy) - floatXr;
                if (frx < 0) frx = 0;
                
                if (fx[0] < flx)
                    fx[0] = flx;
                if (fx[1] < frx)
                    fx[1] = frx;
            }
        }
        return fx;
    }
    
    @Override
    protected void loadSizes()
    {
        loadSizes(false);
    }
    
    @Override
    public void updateSizes()
    {
    	loadSizes(true);
    }
    
    /**
     * Load the sizes from CSS properties.
     * @param update Update mode after the size of the containing box has been updated.
     * The content size is not reset, the margins are recomputed.
     */  
    protected void loadSizes(boolean update)
    {
        CSSDecoder dec = new CSSDecoder(ctx);
        
        //containing box sizes
        float contw = getContainingBlock().width;
        float conth = getContainingBlock().height;
        
        //Borders
        if (!update) //borders needn't be updated
            loadBorders(dec, contw);
        
        //Padding
        loadPadding(dec, contw);
        
        //Position
        loadPosition();
        
        //Content and margins
        if (!update)
        {
        	content = new Dimension(0, 0);
        	margin = new LengthSet();
        	declMargin = new LengthSet();
        }
            
        //Margins, widths and heights
        loadWidthsHeights(dec, contw, conth, update);
        
        if (!update)
        {
        	emargin = new LengthSet(margin);
        	widthAdjust = 0; //reset width adjust when not updating
        }
        else
        { //efficient top and bottom margins already computed; update just left and right
            emargin.left = margin.left;
            emargin.right = margin.right;
        }
        
        //Text indentation
        indent = dec.getLength(getLengthValue("text-indent"), false, 0, 0, contw);
    }
    
    /**
     * Loads the padding sizes from the style.
     * 
     * @param dec CSS decoder used for decoding the style
     * @param contw containing block width for decoding percentages
     */
    protected void loadPadding(CSSDecoder dec, float contw)
    {
        padding = new LengthSet();
        padding.top = dec.getLength(getLengthValue("padding-top"), false, null, null, contw);
        padding.right = dec.getLength(getLengthValue("padding-right"), false, null, null, contw);
        padding.bottom = dec.getLength(getLengthValue("padding-bottom"), false, null, null, contw);
        padding.left = dec.getLength(getLengthValue("padding-left"), false, null, null, contw);
    }
    
    /**
     * Loads the widths and margins from the style.
     * 
     * @param dec CSS decoder used for decoding the style
     * @param contw containing block width for decoding percentages
     */
    protected void loadWidthsHeights(CSSDecoder dec, float contw, float conth, boolean update)
    {
        //Minimal width
        TermLengthOrPercent minw = getLengthValue("min-width");
        TermLengthOrPercent minh = getLengthValue("min-height");
        boolean autoMinW = false; 
        boolean autoMinH = (minh != null && minh.isPercentage() && !getContainingBlockBox().hasFixedHeight());
        min_size = new Dimension(dec.getLength(minw, autoMinW, -1, -1, contw), dec.getLength(minh, autoMinH, -1, -1, conth));
        //Maximal width
        TermLengthOrPercent maxw = getLengthValue("max-width");
        TermLengthOrPercent maxh = getLengthValue("max-height");
        boolean autoMaxW = style.getProperty("max-width") == CSSProperty.MaxWidth.NONE;
        boolean autoMaxH = style.getProperty("max-height") == CSSProperty.MaxHeight.NONE
                || (maxh != null && maxh.isPercentage() && !getContainingBlockBox().hasFixedHeight());
        max_size = new Dimension(dec.getLength(maxw, autoMaxW, -1, -1, contw), dec.getLength(maxh, autoMaxH, -1, -1, conth));
        if (max_size.width != -1 && max_size.width < min_size.width)
            max_size.width = min_size.width;
        if (max_size.height != -1 && max_size.height < min_size.height)
            max_size.height = min_size.height; 

        //Calculate widths and margins
        TermLengthOrPercent width = getLengthValue("width");
        boolean wauto = (width == null) || (style.getProperty("width") == CSSProperty.Width.AUTO);
        computeWidths(width, wauto, true, update);
        if (max_size.width != -1 && content.width > max_size.width)
        {
            width = getLengthValue("max-width");
            computeWidths(width, false, false, update);
        }
        if (min_size.width != -1 && content.width < min_size.width)
        {
            width = getLengthValue("min-width");
            computeWidths(width, false, false, update);
        }
        
        //Calculate heights and margins
        // http://www.w3.org/TR/CSS21/visudet.html#Computing_heights_and_margins
        TermLengthOrPercent height = getLengthValue("height");
        boolean hauto = (height == null) || (style.getProperty("height") == CSSProperty.Height.AUTO);
        computeHeights(height, hauto, true, update);
        if (max_size.height != -1 && content.height > max_size.height)
        {
            height = getLengthValue("max-height");
            computeHeights(height, false, false, update);
        }
        if (min_size.height != -1 && content.height < min_size.height)
        {
            height = getLengthValue("min-height");
            computeHeights(height, false, false, update);
        }
        
        //apply box-sizing if different from content-box
        if (boxSizing == BORDER_BOX)
        {
            if (!wauto)
            {
                content.width -= border.left + border.right + padding.left + padding.right;
                if (content.width < 0) content.width = 0;
            }
            if (!hauto)
            {
                content.height -= border.top + border.bottom + padding.top + padding.bottom;
                if (content.height < 0) content.height = 0;
            }
        }
        
        //apply width adjustment when updating
        if (update && widthAdjust != 0)
        {
            if (!wset)
                content.width += widthAdjust;
            else if (mleftauto)
                margin.left += widthAdjust;
            else
                margin.right += widthAdjust;
        }
    }
    
    /** 
     * Calculates widths and margins according to
     *  http://www.w3.org/TR/CSS21/visudet.html#Computing_widths_and_margins .
     * @param width the specified width or null for auto
     * @param exact true if this is the exact width, false when it's a max/min width
     * @param cblock containing block
     * @param update <code>true</code>, if we're just updating the size to a new containing block size
     */
    protected void computeWidths(TermLengthOrPercent width, boolean auto, boolean exact, boolean update)
    {
        mleftauto = style.getProperty("margin-left") == CSSProperty.Margin.AUTO;
        mrightauto = style.getProperty("margin-right") == CSSProperty.Margin.AUTO;
    	if (position == POS_ABSOLUTE || position == POS_FIXED)
    		computeWidthsAbsolute(width, auto, exact, getContainingBlock().width, update); //containing box created by padding
    	else
    		computeWidthsInFlow(width, auto, exact, getContainingBlock().width, update); //containing block formed by the content only
    }
    
    protected void computeWidthsInFlow(TermLengthOrPercent width, boolean auto, boolean exact, float contw, boolean update)
    {
        CSSDecoder dec = new CSSDecoder(ctx);
        
        if (width == null) auto = true; //no value behaves as 'auto'
        
        TermLengthOrPercent mleft = getLengthValue("margin-left");
        TermLengthOrPercent mright = getLengthValue("margin-right");
        
        if (!widthComputed) update = false;
        
        if (auto)
        {
        	if (exact) wset = false;
            margin.left = dec.getLength(mleft, mleftauto, 0, 0, contw);
            margin.right = dec.getLength(mright, mrightauto, 0, 0, contw);
            declMargin.left = margin.left;
            declMargin.right = margin.right;
            /* For the first time, we always try to use the maximal width even for the
             * boxes out of the flow. When updating, only the in-flow boxes are adjusted. */
            if (!update || isInFlow())
            {
                content.width = contw - margin.left - border.left - padding.left
                                  - padding.right - border.right - margin.right;
                if (content.width < 0) content.width = 0;
            }
        }
        else
        {
        	if (exact) 
        	{
        	    wset = true;
                wrelative = width.isPercentage();
        	}
          	content.width = dec.getLength(width, auto, 0, 0, contw);
            margin.left = dec.getLength(mleft, mleftauto, 0, 0, contw);
            margin.right = dec.getLength(mright, mrightauto, 0, 0, contw);
            declMargin.left = margin.left;
            declMargin.right = margin.right;
            
            //We will prefer some width if the value is not percentage
            boolean prefer = !width.isPercentage();
            
            //Compute the margins if we're in flow and we know the width
            if (isInFlow() && prefer) 
            {
                if (mleftauto && mrightauto)
                {
                    float rest = contw - content.width - border.left - padding.left
                                     - padding.right - border.right;
                    if (rest >= 0)
                    {
                        margin.left = margin.right = rest / 2.0f;
                    }
                    else //negative margin - use it just for the right margin
                    {
                        margin.left = 0;
                        margin.right = rest;
                    }
                }
                else if (mleftauto)
                {
                    margin.left = contw - content.width - border.left - padding.left
                                        - padding.right - border.right - margin.right;
                }
                else if (mrightauto)
                {
                    margin.right = contw - content.width - border.left - padding.left
                                    - padding.right - border.right - margin.left;
                    if (margin.right < 0 && getContainingBlockBox() instanceof BlockBox && ((BlockBox) getContainingBlockBox()).canIncreaseWidth())
                        margin.right = 0;
                }
                else //everything specified, ignore right margin
                {
                    margin.right = contw - content.width - border.left - padding.left
                                    - padding.right - border.right - margin.left;
                    if (margin.right < 0 && getContainingBlockBox() instanceof BlockBox && ((BlockBox) getContainingBlockBox()).canIncreaseWidth())
                        margin.right = 0;
                }
            }
        }
    }

    protected void computeWidthsAbsolute(TermLengthOrPercent width, boolean auto, boolean exact, float contw, boolean update)
    {
        CSSDecoder dec = new CSSDecoder(ctx);
        
    	if (width == null) auto = true; //no value behaves as "auto"

        TermLengthOrPercent mleft = getLengthValue("margin-left");
        TermLengthOrPercent mright = getLengthValue("margin-right");
        
        if (!widthComputed) update = false;
    	
        if (auto)
        {
            if (exact) wset = false;
            if (!update)
                content.width = dec.getLength(width, auto, 0, 0, contw);
        }
        else
        {
            if (exact) 
            {
                wset = true;
                wrelative = width.isPercentage();
            }
            content.width = dec.getLength(width, auto, 0, 0, contw);
        }
    	
    	//count left, right and width constraints
    	int constr = 0;
    	if (wset) constr++;
    	if (leftset) constr++;
    	if (rightset) constr++;
    	
    	//compute margins
    	if (constr < 3)  //too many auto values - auto margins are treated as zero
    	{
	        if (mleftauto)
	            margin.left = 0;
	        else
	            margin.left = dec.getLength(mleft, false, 0, 0, contw);
	        if (mrightauto)
	            margin.right = 0;
	        else
	            margin.right = dec.getLength(mright, false, 0, 0, contw);
    	}
    	else //everything specified
    	{
    	    if (mleftauto && mrightauto)
    	    {
    	        float rest = contw - coords.left - coords.right - border.left - border.right - padding.left - padding.right - content.width;
                margin.left = (rest + 1) / 2;
                margin.right = rest / 2;
    	    }
    	    else if (mleftauto)
    	    {
    	        margin.right = dec.getLength(mright, false, 0, 0, contw);
    	        margin.left = contw - coords.right - border.left - border.right - padding.left - padding.right - content.width - margin.right;
    	    }
    	    else if (mrightauto)
    	    {
    	        margin.left = dec.getLength(mleft, false, 0, 0, contw);
    	        margin.right = contw - coords.right - border.left - border.right - padding.left - padding.right - content.width - margin.left;
    	    }
    	    else //over-constrained, both margins apply (right coordinate will be ignored)
    	    {
                margin.left = dec.getLength(mleft, false, 0, 0, contw);
                margin.right = dec.getLength(mright, false, 0, 0, contw);
    	    }
    	}
    	//for absolute positions, the declared margins correspond to computed ones
    	declMargin.left = margin.left;
    	declMargin.right = margin.right;
    	
    	//compute the letf and right positions
	    if (!leftset && !rightset)
	    {
	        leftstatic = true; //left will be set to static position during the layout
    	    coords.right = contw - coords.left - border.left - border.right - padding.left - padding.right - content.width - margin.left - margin.right;
	    }
	    else if (!leftset)
	    {
    	    coords.left = contw - coords.right - border.left - border.right - padding.left - padding.right - content.width - margin.left - margin.right;
	    }
	    else if (!rightset)
	    {
    	    coords.right = contw - coords.left - border.left - border.right - padding.left - padding.right - content.width - margin.left - margin.right;
	    }
	    else
	    {
	        if (auto) //auto height is computed from the rest
	        	content.width = contw - coords.left - coords.right - border.left - border.right - padding.left - padding.right - margin.left - margin.right;
	        else //over-constrained - compute the right coordinate
	        	coords.right = contw - coords.left - border.left - border.right - padding.left - padding.right - content.width - margin.left - margin.right;
	    }
    }
    
    /** 
     * Calculates heights and margins according to
     *  http://www.w3.org/TR/CSS21/visudet.html#Computing_heights_and_margins
     * @param height the specified width
     * @param exact true if this is the exact height, false when it's a max/min height
     * @param update <code>true</code>, if we're just updating the size to a new containing block size
     */
    protected void computeHeights(TermLengthOrPercent height, boolean auto, boolean exact, boolean update)
    {
        final Rectangle cb = getContainingBlock(); 
        if (position == POS_ABSOLUTE || position == POS_FIXED)
            computeHeightsAbsolute(height, auto, exact, cb.width, cb.height, update);
        else
            computeHeightsInFlow(height, auto, exact, cb.width, cb.height, update);
        //the computed margins allways correspond to the declared ones
        declMargin.top = margin.top;
        declMargin.bottom = margin.bottom;
    }
    
    protected void computeHeightsInFlow(TermLengthOrPercent height, boolean auto, boolean exact, float contw, float conth, boolean update)
    {
        CSSDecoder dec = new CSSDecoder(ctx);
        
        if (height == null) auto = true;

        boolean mtopauto = style.getProperty("margin-top") == CSSProperty.Margin.AUTO;
        TermLengthOrPercent mtop = getLengthValue("margin-top");
        boolean mbottomauto = style.getProperty("margin-bottom") == CSSProperty.Margin.AUTO;
        TermLengthOrPercent mbottom = getLengthValue("margin-bottom");
        
        if (!auto)
        {
            if (exact)
                hset = true;
            if (!update)
                content.height = dec.getLength(height, false, 0, 0, conth);
        }
        else //height not explicitly set
        {
            if (exact)
                hset = false;
        }
        
        //compute margins - auto margins are treated as zero
        if (mtopauto)
            margin.top = 0;
        else
            margin.top = dec.getLength(mtop, false, 0, 0, contw); //contw is ok here!
        if (mbottomauto)
            margin.bottom = 0;
        else
            margin.bottom = dec.getLength(mbottom, false, 0, 0, contw);
    }
    
    protected void computeHeightsAbsolute(TermLengthOrPercent height, boolean auto, boolean exact, float contw, float conth, boolean update)
    {
        CSSDecoder dec = new CSSDecoder(ctx);
        
    	if (height == null) auto = true; //no value behaves as "auto"

    	boolean mtopauto = style.getProperty("margin-top") == CSSProperty.Margin.AUTO;
        TermLengthOrPercent mtop = getLengthValue("margin-top");
        boolean mbottomauto = style.getProperty("margin-bottom") == CSSProperty.Margin.AUTO;
        TermLengthOrPercent mbottom = getLengthValue("margin-bottom");
    	
        if (!auto && height != null)
        {
            hset = exact; //not a percentage - it's a fixed height
            if (!update)
                content.height = dec.getLength(height, auto, 0, 0, conth);
        }
        else //height not explicitly set
        {
            hset = false;
        }
    	
    	//count top, bottom and height constraints
    	int constr = 0;
    	if (hset) constr++;
    	if (topset) constr++;
    	if (bottomset) constr++;
    	
    	//compute margins
    	if (constr < 3)  //too many auto values - auto margins are treated as zero
    	{
	        if (mtopauto)
	            margin.top = 0;
	        else
	            margin.top = dec.getLength(mtop, false, 0, 0, contw); //contw is ok here!
	        if (mbottomauto)
	            margin.bottom = 0;
	        else
	            margin.bottom = dec.getLength(mbottom, false, 0, 0, contw);
    	}
    	else //absolutely positioned, everything specified
    	{
    	    if (mtopauto && mbottomauto)
    	    {
    	        float rest = conth - coords.top - coords.bottom - border.top - border.bottom - padding.top - padding.bottom - content.height;
                margin.top = (rest + 1) / 2;
                margin.bottom = rest / 2;
    	    }
    	    else if (mtopauto)
    	    {
    	        margin.bottom = dec.getLength(mbottom, false, 0, 0, contw);
    	        margin.top = conth - coords.top - coords.bottom - border.top - border.bottom - padding.top - padding.bottom - content.height - margin.bottom;
    	    }
    	    else if (mbottomauto)
    	    {
                margin.top = dec.getLength(mtop, false, 0, 0, contw);
                margin.bottom = conth - coords.top - coords.bottom - border.top - border.bottom - padding.top - padding.bottom - content.height - margin.top;
    	    }
    	    else //over-constrained, both margins apply (bottom will be ignored)
    	    {
                margin.top = dec.getLength(mtop, false, 0, 0, contw);
                margin.bottom = dec.getLength(mbottom, false, 0, 0, contw);
    	    }
    	}
    	
    	//compute the top and bottom
    	LengthSet m = update ? emargin : margin; //use the efficient margins instead of the declared ones when the efficient have been computed
	    if (!topset && !bottomset)
	    {
	        topstatic = true; //top will be set to static position during the layout
            coords.bottom = conth - coords.top - border.top - border.bottom - padding.top - padding.bottom - m.top - m.bottom - content.height;
	    }
	    else if (!topset)
	    {
            coords.top = conth - coords.bottom - border.top - border.bottom - padding.top - padding.bottom - m.top - m.bottom - content.height;
	    }
	    else if (!bottomset)
	    {
            coords.bottom = conth - coords.top - border.top - border.bottom - padding.top - padding.bottom - m.top - m.bottom - content.height;
	    }
	    else
	    {
	        if (auto) //auto height is computed from the rest
	            content.height = conth - coords.top - coords.bottom - border.top - border.bottom - padding.top - padding.bottom - m.top - m.bottom;
	        else //over-constrained - compute the bottom coordinate
	        	coords.bottom = conth - coords.top - border.top - border.bottom - padding.top - padding.bottom - m.top - m.bottom - content.height;
	    }
    }
    
    /**
     * Checks if the box content is separated from the top margin by a border or padding.
     */
    protected boolean separatedFromTop(ElementBox box)
    {
        return (box.border.top > 0 || box.padding.top > 0 || box.isRootElement());
    }
    
    /**
     * Checks if the box content is separated from the bottom margin by a border or padding.
     */
    protected boolean separatedFromBottom(ElementBox box)
    {
        return (box.border.bottom > 0 || box.padding.bottom > 0 || box.isRootElement());
    }
    
    /**
     * Computes the collapsed margin height from two adjoining margin heights.
     * @see http://www.w3.org/TR/CSS22/box.html#collapsing-margins
     * @param m1 The first margin height
     * @param m2 The second margin height
     * @return The collapsed margin height
     */
    protected float collapsedMarginHeight(float m1, float m2)
    {
        if (m1 >= 0 && m2 >= 0)
            return Math.max(m1, m2);
        else if (m1 < 0 && m2 < 0)
            return Math.min(m1, m2);
        else
            return m1 + m2;
    }
    
    /**
     * Remove the previously splitted child boxes
     */
    protected void clearSplitted()
    {
        for (Iterator<Box> it = nested.iterator(); it.hasNext(); )
        {
            Box box = it.next();
            if (box.splitted)
            {
                it.remove();
                endChild--;
            }
        }
    }
    
} abstract public class Box
{
    /** Is this a box for the root element? */
    protected boolean rootelem;
    
    /** Is this box a block? */
    protected boolean isblock;
    
    /** Is this box empty? (doesn't contain any visible non-empty block */
    protected boolean isempty;
    
    /** Is this element displayed? (it has not display: none) */
    protected boolean displayed; 

    /** Is this element visible? (it has not visibility: hidden) */
    protected boolean visible;
    
    /** Should this box be preserved in the tree even if it is empty and not visible? (for special-purpose boxes, e.g. anchors */
    protected boolean sticky;
    
    /** The DOM node that forms this box. It is either an inline element
     * (e.g. <em>) or a text node (anonymous box) */
    protected Node node;
    
    /** The order of the node in the code (first node is 0) */
    protected int order;
    
    /** Box position on the screen relatively to the containing content box.
     * Coordinates of the whole box including margin. */
    protected Rectangle bounds;
    
    /** Absolute box position (on the whole page) */
    protected Rectangle absbounds;
    
    /** The viewport */
    protected Viewport viewport;
    
    /** Parent box */
    protected ElementBox parent;
    
    /** The box that generates the containing block.
     * @see <a href="http://www.w3.org/TR/CSS21/visudet.html#containing-block-details">http://www.w3.org/TR/CSS21/visudet.html#containing-block-details</a> */
    protected ElementBox cbox;
    
    /** Clipping box. The box is not visible if it is not inside of the clipping box.
     * Normally, the clipping box is the viewport or the nearest parent with
     * overflow set to hidden */
    protected BlockBox clipblock;
    
    /** The parent box of the stacking context where this box is contained */
    protected ElementBox stackingParent;
    
    /** Maximal total width for the layout (obtained from the owner box) */
    protected float availwidth;
    
    /** Rendering context (em size etc.) */
    protected VisualContext ctx;
    
    /** Base URL */
    protected URL base;
    
    /** True if this box is a result of splitting */
    protected boolean splitted;
    
    /** a serial number of the splitted box copy (original is 0) */
    protected int splitid;
    
    /** Remaining part of the box after splitting */
    protected Box rest;
    
    //==============================================================
    
    /**
     * Create a new instance of a box
     * @param n the DOM node that forms this box
     * @param g current graphics context
     * @param ctx current visual context
     */
    public Box(Node n, VisualContext ctx)
    {
        this.ctx = ctx;
        node = n;
        rootelem = false;
        isblock = false;
        isempty = true;
        sticky = false;

        bounds = new Rectangle();
        absbounds = new Rectangle();
        displayed = true;
        visible = true;
        splitted = false;
        splitid = 0;
        rest = null;
    }

    /**
     * Copy all the values from another box
     * @param src source box
     */
    public void copyValues(Box src)
    {
        rootelem = src.rootelem;
        isblock = src.isblock;
        order = src.order;
        isempty = src.isempty;
        sticky = src.sticky;
        availwidth = src.availwidth;
        viewport = src.viewport;
        parent = src.parent;
        cbox = src.cbox;
        clipblock = src.clipblock;

        bounds = new Rectangle(src.bounds);
        absbounds = new Rectangle(src.absbounds);
        displayed = src.displayed;
        visible = src.visible;
        splitted = src.splitted;
        rest = src.rest;
    }
    
    /**
     * Provides the internal object initialization, after the box tree has been completed. 
     * Should be used fpr subtree content organization, etc. Called from {@link #initSubtree()} during
     * the tree initialization. 
     */
    protected void initBox()
    {
    }
    
    /**
     * Provides the initialization of the box and the corresponding subtree (if any).
     * Loads the basic CSS properties and computes the sizes.
     */
    public void initSubtree()
    {
        initBox(); //default implementation - just initialize this box
    }
    
    /**
     * Initializes a box in order to be a proper child box of the specified parent. Copies
     * all the necessary information from the parent.
     * @param parent the parent box
     */
    public void adoptParent(ElementBox parent)
    {
        if (parent instanceof BlockBox)
            setContainingBlockBox(parent);
        else
            setContainingBlockBox(parent.getContainingBlockBox());
        setParent(parent);
        setViewport(parent.getViewport());
        setClipBlock(parent.getClipBlock());
    }
    
    //========================================================================
        
    /**
     * Returns the DOM node that forms this box.
     * @return the DOM node
     */
    public Node getNode()
    {
        return node;
    }
    
    /**
     * Gets the order of the node in the document.
	 * @return the order
	 */
	public int getOrder()
	{
		return order;
	}

	/**
     * Sets the order of the node in the document.
	 * @param order the order to set
	 */
	public void setOrder(int order)
	{
		this.order = order;
	}

	/**
	 * Obtains the order of the box copy.
	 * @return the copy number (0 means original non-splitted box)
	 */
	public int getSplitId()
	{
	    return splitid;
	}
	
    /**
     * @return the visual context of this box
     */
    public VisualContext getVisualContext()
    {
        return ctx;
    }
    
    /**
     * Checks if the box corresponds to the root element
     * @return true, if the box corresponds to the root element
     */
    public boolean isRootElement()
    {
        return rootelem;
    }

    /**
     * Makes this box a root element box.
     */
    public void makeRoot()
    {
        this.rootelem = true;
    }

    /** 
     * Checks if this is a block box.
     * @return false if this is an inline box and it contains inline
     * boxes only, true otherwise.
     */
    public boolean isBlock()
    {
        return isblock;
    }
    
    /** 
     * @return <code>true</code>, if this element contains no visible non-empty elements
     */
    public boolean isEmpty()
    {
        return isempty;
    }
    
    /**
     * @return <code>true</code>, if this element has the 'display' property different from 'none'
     */
    public boolean isDisplayed()
    {
        return displayed;
    }

    /**
     * Checks if this box is visible, i.e. it has not visibility:hidden and it is at least partially
     * contained in the clipping region.
     * @return <code>true</code> if the box is visible
     */
    public boolean isVisible()
    {
        return visible && clipblock.isVisible() && clipblock.getClippedContentBounds().intersects(getAbsoluteBounds());
    }
    
    /**
     * Checks if the box has visibility property set to visible.
     * @return <code>true</code>, if this element has the 'visibility' property set to 'visible'
     */
    public boolean isDeclaredVisible()
    {
        return visible;
    }
    
    /**
     * Checks whether the box should be preserved in the tree even if it is empty and collapsed. This should be used
     * for special-purpose boxes such as link anchors.
     * @return <code>true</code> for sticky boxes
     */
    public boolean isSticky()
    {
        return sticky;
    }

    /**
     * Sets whether the box should be preserved in the tree even if it is empty and collapsed. This should be used
     * for special-purpose boxes such as link anchors.
     * @param sticky <code>true</code> for sticky boxes
     */
    public void setSticky(boolean sticky)
    {
        this.sticky = sticky;
    }

    /**
     * @return <code> true if this is a replaced box
     */
    public boolean isReplaced()
    {
    	return false;
    }
    
    /**
     * @return all the text contained in this box and its subboxes
     */
    abstract public String getText();
    
    /**
     * @return <code>true</code> if the box only contains whitespaces
     */
    abstract public boolean isWhitespace();

    /**
     * Checks whether the whitespaces should be collapsed within in the box according to its style.
     * @return <code>true</code> if the whitespace sequences should be collapsed.
     */
    abstract public boolean collapsesSpaces();
    
    /**
     * Checks whether this element should preserve the line breaks according to its style.
     * @return <code>true</code> when the line breaks should be preserved
     */
    abstract public boolean preservesLineBreaks();
    
    /**
     * Checks whether this box allows line wrapping on whitespaces according to the whit-space setting.
     * @return <code>true</code> when line wrapping is allowed
     */
    abstract public boolean allowsWrapping();
    
    /**
     * @return <code>true</code> if the box can be split in two or more boxes
     * on different lines
     */
    abstract public boolean canSplitInside();
    
    /**
     * @return <code>true</code> if there can be a linebreak before this element
     * on different lines
     */
    abstract public boolean canSplitBefore();
    
    /**
     * @return <code>true</code> if there can be a linebreak after this element
     * on different lines
     */
    abstract public boolean canSplitAfter();
    
    /**
     * Checks whether the box content starts with a whitespace character
     * @return <code>true</code> if the box content is not empty and it starts with a whitespace character
     */
    abstract public boolean startsWithWhitespace();
    
    /**
     * Checks whether the box content ends with a whitespace character
     * @return <code>true</code> if the box content is not empty and it starts with a whitespace character
     */
    abstract public boolean endsWithWhitespace();
    
    /**
     * Switches ignoring the initial whitespace during the box layout on/off
     * @param b when set to <code>true</code> the initial whitespace characters will be ignored
     */
    abstract public void setIgnoreInitialWhitespace(boolean b);
    
    /**
     * Set the box position.
     * @param x the x coordinate
     * @param y the y coordinate
     */
    public void setPosition(float x, float y)
    {
        bounds.setLocation(x, y);
    }

    /**
     * Move the box to the right.
     * @param ofs distance in pixels - positive numbers move to the right, negative to the left
     */
    public void moveRight(float ofs)
    {
        bounds.x += ofs;
    }
    
    /**
     * Move the box down.
     * @param ofs distance in pixels - positive numbers move down, negative up
     */
    public void moveDown(float ofs)
    {
        bounds.y += ofs;
    }
    
    /**
     * Set the box total width and height.
     * @param w total box width including margins and borders
     * @param h total height
     */
    public void setSize(float w, float h)
    {
        bounds.setSize(w, h);
    }
    
    /**
     * Returns the real width of the box computed during the layout.
     * @return total width including margins and borders.
     */ 
    public float getWidth()
    {
        return bounds.width;
    }
    
    /**
     * Returns the real height of the box computed during the layout.
     * @return total height including margins and borders
     */ 
    public float getHeight()
    {
        return bounds.height;
    }
    
    /**
     * Returns maximal box bounds including all borders and margins.
     * @return Box bounds
     */
    public Rectangle getBounds()
    {
        return bounds;
    }
    
    /**
     * Returns the absolute box position on the page
     * @return Absolute box bounds
     */
    public Rectangle getAbsoluteBounds()
    {
    	return absbounds;
    }
    

    /**
     * Adjusts the absolute bounds width and height in order to fit into the clip.
     * If the box doesn't fit at all, it is marked as invisible.
     */
    public void clipAbsoluteBounds(Rectangle clip)
    {
        Rectangle inter = absbounds.intersection(clip);
        if (inter.width == 0 && inter.height == 0)
            displayed = false;
        else
            absbounds = inter;
    }
    
    /**
     * Computes the absolute bounds after clipping by the appropriate clipping box. 
     * @return the clipped absolute bounds
     */
    public Rectangle getClippedBounds()
    {
        if (clipblock == null)
            return getAbsoluteBounds();
        else
            return clipblock.getClippedContentBounds().intersection(getAbsoluteBounds());
    }

    /**
     * @return maximal width that was available for the box placement during the layout processing
     */
    public float getAvailableWidth()
    {
        return availwidth;
    }
    
    /**
     * Set the maximal width available to the box
     * @param availw the maximal available width
     */
    public void setAvailableWidth(float availw)
    {
        availwidth = availw;
    }
    
    /**
     * Obtains the containing block bounds.
     * @return the containing block bounds.
     */
    public Rectangle getContainingBlock()
    {
        if (cbox instanceof Viewport) //initial containing block
        {
            Rectangle visible = ((Viewport) cbox).getVisibleRect();
            return new Rectangle(0, 0, visible.width, visible.height);
        }
        else //static or relative position
            return cbox.getContentBounds();
    }
    
    /**
     * Obtains the containing block absolute bounds.
     * @return the containing block absolute bounds.
     */
    public Rectangle getAbsoluteContainingBlock()
    {
        if (cbox instanceof Viewport) //initial containing block
        {
            Rectangle ab = cbox.getAbsoluteBounds(); //normally positioned at 0,0; other value for nested viewports (e.g. objects)
            Rectangle visible = ((Viewport) cbox).getVisibleRect();
            return new Rectangle(ab.x, ab.y, visible.width, visible.height);
        }
        else //static or relative position
            return cbox.getAbsoluteContentBounds();
    }
    
    /**
     * Obtains the box that generates the containing block for this box.
     * @return The corresponding generating box according to the 
     * <a href="http://www.w3.org/TR/CSS21/visudet.html#containing-block-details">CSS specification</a>
     */
    public ElementBox getContainingBlockBox()
    {
        return cbox;
    }
    
    /**
     * Sets the box that generates the containing block. During the layout, the box position will be
     * computed based on the containing block.
     * @param box the containing box
     */
    public void setContainingBlockBox(ElementBox box)
    {
        cbox = box;
    }
    
    /**
     * Determines the clipping box. The box is not visible if it is not inside of the clipping box. 
     * Normally, the clipping box is the viewport or the nearest parent with overflow set to hidden. 
     * @return the clipping block
     */
    public BlockBox getClipBlock()
    {
        return clipblock;
    }

    /**
     * Sets the clipping block.
     * @param clipblock the clipblock to set
     */
    public void setClipBlock(BlockBox clipblock)
    {
        this.clipblock = clipblock;
    }
    
    /**
     * @return the expected width of the box according to the CSS property values
     */ 
    abstract public float totalWidth();
    
    /**
     * @return the expected height of the box according to the CSS property values
     */ 
    abstract public float totalHeight();
    
    /**
     * @return maximal available width of the content during the layout
     */
    abstract public float getAvailableContentWidth();
    
    /**
     * @return the X coordinate of the content box top left corner
     */
    abstract public float getContentX();
    
    /**
     * @return the absolute X coordinate of the content box top left corner
     */
    abstract public float getAbsoluteContentX();
    
    /**
     * @return the Y coordinate of the content box top left corner
     */
    abstract public float getContentY();

    /**
     * @return the Y coordinate of the content box top left corner
     */
    abstract public float getAbsoluteContentY();
    
    /**
     * @return the width of the content without any margins and borders
     */
    abstract public float getContentWidth();
    
    /**
     * @return the height of the content without any margins and borders
     */
    abstract public float getContentHeight();
    
    /**
     * @return the bounds of the content box
     */
    public Rectangle getContentBounds()
    {
        return new Rectangle(getContentX(), getContentY(), getContentWidth(), getContentHeight());
    }
    
    /**
     * @return the absolute bounds of the content box
     */
    public Rectangle getAbsoluteContentBounds()
    {
        return new Rectangle(getAbsoluteContentX(), getAbsoluteContentY(), getContentWidth(), getContentHeight());
    }
    
    /**
     * Determines the minimal width in which the element can fit.
     * @return the minimal width
     */
    abstract public float getMinimalWidth();
    
    /**
     * 
     * Determines the maximal width of the element according to its contents.
     * @return the maximal width
     */
    abstract public float getMaximalWidth();
    
    /**
     * Determines the minimal bounds of the really displayed content.
     * @return the minimal bounds
     */
    abstract public Rectangle getMinimalAbsoluteBounds();
    
    /**
     * @return true, if the box is in-flow
     */
    abstract public boolean isInFlow();

    /**
     * @return <code>true</code> if the width of the box is either explicitely
     * set or it can be computed from the parent box
     */
    abstract public boolean hasFixedWidth();
    
    /**
     * @return <code>true</code> if the height of the box is either explicitely
     * set or it can be computed from the parent box
     */
    abstract public boolean hasFixedHeight();
    
    /**
     * @return true, if the box contains any in-flow boxes
     */
    abstract public boolean containsFlow();
    
    /**
     * @return true, if the element displays at least something (some content,
     * or borders) 
     */
    abstract public boolean affectsDisplay();

    /**
	 * @return the viewport
	 */
	public Viewport getViewport()
	{
		return viewport;
	}

	/**
	 * @param viewport the viewport to set
	 */
	public void setViewport(Viewport viewport)
	{
		this.viewport = viewport;
	}

	/**
	 * @return Returns the parent.
	 */
	public ElementBox getParent()
	{
		return parent;
	}

	/**
	 * @param parent The parent to set.
	 */
	public void setParent(ElementBox parent)
	{
		this.parent = parent;
	}

	/**
	 * Obtains the parent box of the stacking context this box belongs to.
	 * @return the element that creates current stacking context
	 */
	public ElementBox getStackingParent()
    {
        return stackingParent;
    }

	/**
	 * Sets the box that forms the stacking context for this box.
	 * @param stackingParent parent box to set
	 */
    public void setStackingParent(ElementBox stackingParent)
    {
        this.stackingParent = stackingParent;
    }

    /**
     * Updates the stacking parent values and registers the z-index for this parent.
     */
    protected void updateStackingContexts()
    {
        if (parent != null)
        {
            if (parent.formsStackingContext())
                stackingParent = parent;
            else
                stackingParent = parent.getStackingParent();
        }
    }
    
    /**
     * @return the base URL
     */
    public URL getBase()
    {
        return base;
    }

    /**
     * @param base the base URL to set
     */
    public void setBase(URL base)
    {
        this.base = base;
    }

    /**
	 * When the box doesn't fit to the line, it can be split in two boxes.
	 * The first one remains at its place and the rest must be placed elsewhere.
	 * The splitting algorithm depends on the box type.
	 * @return the rest of the box
	 */
    protected Box getRest()
    {
        return rest;
    }
    
    //========================================================================
    
    /** 
     * Compute the width and height of this element. Layout the sub-elements.
     * @param availw Maximal width available to the child elements
     * @param force Use the area even if the used width is greater than maxwidth
     * @param linestart Indicates whether the element is placed at the line start
     * @return True if the box has been succesfully placed
     */
    abstract public boolean doLayout(float availw, boolean force, boolean linestart);
    

    /** 
     * Calculate absolute positions of all the subboxes.
     */
    abstract public void absolutePositions();
    
    
    //=======================================================================
    
    /** 
     * Draw the box and all the subboxes on the default
     * graphics context passed to the box constructor. 
     */
    /*public void draw()
    {
        draw(g);
    }*/
    
    /**
     * Draw the box and all the subboxes.
     * @param g graphics context to draw on
     */
    /*public void draw(Graphics2D g);
    {
        if (isVisible())
        {
            draw(g, DrawStage.DRAW_NONFLOAT, DrawMode.DRAW_BOTH);
            draw(g, DrawStage.DRAW_FLOAT, DrawMode.DRAW_BOTH);
            draw(g, DrawStage.DRAW_NONFLOAT, DrawMode.DRAW_FG);
        }
    }*/
    
    /**
     * Draws the specified stage.
     * @param turn drawing stage
     */
    abstract public void draw(DrawStage turn);

    //=========================================================================================
    
    /**
     * The box hash code is given by its serial number and its copy ID.
     */
    @Override
    public int hashCode()
    {
        final int prime = 31;
        int result = 1;
        result = prime * result + order;
        result = prime * result + splitid;
        return result;
    }

    /**
     * The boxes are equal if they have the some serial number and the same copy ID.
     */
    @Override
    public boolean equals(Object obj)
    {
        if (this == obj) return true;
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        Box other = (Box) obj;
        if (order != other.order) return false;
        if (splitid != other.splitid) return false;
        return true;
    }
    
    //=========================================================================================
    
    /**
     * Box tree drawing stage according to CSS specification.
     */
    public enum DrawStage 
    {
        /** stage 3: the in-flow, non-inline-level, non-positioned descendants */ 
        DRAW_NONINLINE,
        /** stage 4: the non-positioned floats */
        DRAW_FLOAT,
        /** stage 5: the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks */
        DRAW_INLINE
    }
    
} public class BrowserConfig
{
    private static Logger log = LoggerFactory.getLogger(BrowserConfig.class);
    
    public static final String SERIF = "serif";
    public static final String SANS_SERIF = "sans-serif";
    public static final String MONOSPACE = "monospace";
    public static final String CURSIVE = "cursive";
    public static final String FANTASY = "fantasy";

    /** The viewport background color */
    private Color viewportBackgroundColor;
    
    /** Should we load the external images? */
    private boolean loadImages;
    
    /** Should we load the CSS background images? */
    private boolean loadBackgroundImages;
    
    /** Should we load CSS fonts? */
    private boolean loadFonts;
    
    /** Image loading timeout [ms] */
    private int imageLoadTimeout;
    
    /** Should we interpret HTML tags? */
    private boolean useHTML;
    
    /** Should we replace the images with their ALT attribute? */
    private boolean replaceImagesWithAlt;
    
    /** Should the viewport clip its contents? */
    private boolean clipViewport;
    
    /** Registered DocumentSource implementation */
    private Class<? extends DocumentSource> documentSourceClass;
    
    /** Registered DOMSource implementation */
    private Class<? extends DOMSource> domSourceClass;
    
    /** Registered content observer that tracks the image loading */
    private ContentObserver contentObserver;

    private ImageCache imageCache;
    
    /** Logical font mapping */
    private Map<String, List<String>> logicalFonts;
    
    /**
     * Creates a new config with default values of the options.
     */
    public BrowserConfig()
    {
        viewportBackgroundColor = new Color(255, 255, 255);
        loadImages = true;
        loadBackgroundImages = true;
        loadFonts = true;
        imageLoadTimeout = 500;
        useHTML = true;
        replaceImagesWithAlt = false;
        clipViewport = false;
        documentSourceClass = DefaultDocumentSource.class;
        domSourceClass = DefaultDOMSource.class;
        contentObserver = null;
        logicalFonts = getDefaultLogicalFonts();
    }

    public Color getViewportBackgroundColor()
    {
        return viewportBackgroundColor;
    }

    /**
     * Sets the background color of the viewport. This color is used when no background color
     * is defined for the root element (and neither the body element for HTML mode). Default
     * walue is white.
     * @param viewportBackgroundColor
     */
    public void setViewportBackgroundColor(Color viewportBackgroundColor)
    {
        this.viewportBackgroundColor = viewportBackgroundColor;
    }

    public boolean getLoadImages()
    {
        return loadImages;
    }

    /**
     * Sets whether to load the referenced content images automatically. The default value is <code>true</code>.
     * @param loadImages
     */
    public void setLoadImages(boolean loadImages)
    {
        this.loadImages = loadImages;
    }

    public boolean getLoadBackgroundImages()
    {
        return loadBackgroundImages;
    }

    /**
     * Sets whether to load the CSS fonts automatically. The default value is <code>true</code>.
     * @param loadImages
     */
    public void setLoadFonts(boolean loadFonts)
    {
        this.loadFonts = loadFonts;
    }

    public boolean isLoadFonts()
    {
        return loadFonts;
    }

    /**
     * Sets whether to load the CSS background images automatically. The default value is <code>true</code>.
     * @param loadBackgroundImages
     */
    public void setLoadBackgroundImages(boolean loadBackgroundImages)
    {
        this.loadBackgroundImages = loadBackgroundImages;
    }

    public int getImageLoadTimeout()
    {
        return imageLoadTimeout;
    }

    /**
     * Configures the timeout for loading images. The default value is 500ms.
     * @param imageLoadTimeout The timeout for loading images in miliseconds.
     */
    public void setImageLoadTimeout(int imageLoadTimeout)
    {
        this.imageLoadTimeout = imageLoadTimeout;
    }

    /**
     * Registers the content observer that tracks the image loading.
     * @param contentObserver the content observer to be used or {@code null} for none.
     */
    public void setContentObserver(ContentObserver contentObserver)
    {
        this.contentObserver = contentObserver;
    }

    public ContentObserver getContentObserver()
    {
        return contentObserver;
    }

    public ImageCache getImageCache()
    {
        return imageCache;
    }

    public void setImageCache(ImageCache imageCache) {
        this.imageCache = imageCache;
    }

    public boolean getUseHTML()
    {
        return useHTML;
    }

    /**
     * Sets whether the engine should use the HTML extensions or not. Currently, the HTML
     * extensions include the following:
     * <ul>
     * <li>Creating replaced boxes for <code>&lt;img&gt;</code> elements
     * <li>Using the <code>&lt;body&gt;</code> element background for the whole canvas according to the HTML specification
     * </ul> 
     * @param useHTML <code>false</code> if the extensions should be switched off (default is on)
     */
    public void setUseHTML(boolean useHTML)
    {
        this.useHTML = useHTML;
    }

    public boolean getReplaceImagesWithAlt()
    {
        return replaceImagesWithAlt;
    }

    /**
     * Sets whether the images should be replaced by their 'alt' text.
     * @param replaceImagesWithAlt when set to {@code true}, the images will be treated as their 'alt' text.
     */
    public void setReplaceImagesWithAlt(boolean replaceImagesWithAlt)
    {
        this.replaceImagesWithAlt = replaceImagesWithAlt;
    }

    public boolean getClipViewport()
    {
        return clipViewport;
    }

    /**
     * Configures whether the rendered page should be clipped by the viewport. When set to {@code true},
     * the content outside the viewport won't be rendered (the generated boxes won't be visible).
     * When set to {@code false}, the whole page will be rendered and the viewport size is only
     * used for layout computation (positioned boxes etc.) The default is {@code false}.
     * @param clipViewport The configuration value.
     */
    public void setClipViewport(boolean clipViewport)
    {
        this.clipViewport = clipViewport;
    }

    /**
     * Sets the class used by CSSBox for obtaining documents based on their URLs.
     * @param documentSourceClass the new document source class
     */
    public void registerDocumentSource(Class<? extends DocumentSource> documentSourceClass)
    {
        this.documentSourceClass = documentSourceClass;
    }
    
    /**
     * Obtains the class used by CSSBox for obtaining documents based on their URLs.
     * @return the used class
     */
    public Class<? extends DocumentSource> getDocumentSourceClass()
    {
        return documentSourceClass;
    }
    
    /**
     * Creates a new instance of the {@link org.fit.cssbox.io.DocumentSource} class registered in the browser configuration.
     * @param url the URL to be given to the document source.
     * @return the document source.
     * @throws IOException 
     */
    public DocumentSource createDocumentSource(URL url) throws IOException
    {
        try
        {
            Constructor<? extends DocumentSource> constr = getDocumentSourceClass().getConstructor(URL.class);
            return constr.newInstance(url);
        } catch (Exception e) {
            Throwable cause = e; //find if there is an IOException cause and throw it
            while (cause != null && !(cause instanceof IOException))
                cause = e.getCause();
            if (cause != null && cause instanceof IOException)
                throw (IOException) cause;
            //no IO exception cause, this should not happen (some internal reflection problem)
            log.error("Could not create the DocumentSource instance: " + e.getMessage());
            e.printStackTrace();
            return null;
        }
    }
    
    /**
     * Creates a new instance of the {@link org.fit.cssbox.io.DocumentSource} class registered in the browser configuration.
     * @param base the base URL
     * @param urlstring the URL suffix
     * @return the document source.
     */
    public DocumentSource createDocumentSource(URL base, String urlstring)
    {
        try
        {
            Constructor<? extends DocumentSource> constr = getDocumentSourceClass().getConstructor(URL.class, String.class);
            return constr.newInstance(base, urlstring);
        } catch (Exception e) {
            log.warn("Could not create the DocumentSource instance: " + e.getMessage());
            return null;
        }
    }
    
    /**
     * Sets the class used by CSSBox for the DOM tree from documents.
     * @param domSourceClass the new DOM source class
     */
    public void registerDOMSource(Class<? extends DOMSource> domSourceClass)
    {
        this.domSourceClass = domSourceClass;
    }
    
    /**
     * Obtains the class used by CSSBox for the DOM tree from documents.
     * @return the used class
     */
    public Class<? extends DOMSource> getDOMSourceClass()
    {
        return domSourceClass;
    }

    /**
     * Creates a new instance of the {@link org.fit.cssbox.io.DOMSource} class registered in the browser configuration
     * ({@link org.fit.cssbox.layout.BrowserConfig}).
     * @param src the document source to be given to the DOM source.
     * @return the DOM source.
     */
    public DOMSource createDOMSource(DocumentSource src)
    {
        try
        {
            Constructor<? extends DOMSource> constr = getDOMSourceClass().getConstructor(DocumentSource.class);
            return constr.newInstance(src);
        } catch (Exception e) {
            log.warn("BoxFactory: Warning: could not create the DOMSource instance: " + e.getMessage());
            return null;
        }
    }
    
    /**
     * Sets a default physical font to be used for a logical name.
     * @param logical the logical font name
     * @param physical the physical font to be used
     */
    public void setLogicalFont(String logical, List<String> physical)
    {
        logicalFonts.put(logical.toLowerCase(), physical);
    }
    
    /**
     * Obtains the physical font name used for a logical name.
     * @param logical the logical name
     * @return a list of the physical font names. It may be empty when no alternatives are available.
     */
    public List<String> getLogicalFont(String logical)
    {
        List<String> ret = logicalFonts.get(logical.toLowerCase());
        if (ret == null)
            ret = Collections.emptyList();
        return ret;
    }
 
    /**
     * Initializes the default logical font table. It maps the logical CSS fonts to the
     * same AWT logical fonts. This is good when the result is rendered using the
     * AWT rendering engine (GraphicsRenderingEngine) but it should be replaced
     * by physical font alternatives in other cases (e.g. the PDF output).
     * @return the default font table
     */
    protected Map<String, List<String>> getDefaultLogicalFonts()
    {
        Map<String, List<String>> ret = new HashMap<>();
        ret.put(SERIF, Arrays.asList("Serif"));
        ret.put(SANS_SERIF, Arrays.asList("SansSerif"));
        ret.put(MONOSPACE, Arrays.asList("Monospaced"));
        return ret;
    }
    
} abstract public class ElementBox extends Box
{
    private static Logger log = LoggerFactory.getLogger(ElementBox.class);
    
    public static final CSSProperty.Display DISPLAY_ANY = null;
    public static final CSSProperty.Display DISPLAY_NONE = CSSProperty.Display.NONE;
    public static final CSSProperty.Display DISPLAY_INLINE = CSSProperty.Display.INLINE;
    public static final CSSProperty.Display DISPLAY_BLOCK = CSSProperty.Display.BLOCK;
    public static final CSSProperty.Display DISPLAY_LIST_ITEM = CSSProperty.Display.LIST_ITEM;
    public static final CSSProperty.Display DISPLAY_RUN_IN = CSSProperty.Display.RUN_IN;
    public static final CSSProperty.Display DISPLAY_INLINE_BLOCK = CSSProperty.Display.INLINE_BLOCK;
    public static final CSSProperty.Display DISPLAY_TABLE = CSSProperty.Display.TABLE;
    public static final CSSProperty.Display DISPLAY_INLINE_TABLE = CSSProperty.Display.INLINE_TABLE;
    public static final CSSProperty.Display DISPLAY_TABLE_ROW_GROUP = CSSProperty.Display.TABLE_ROW_GROUP;
    public static final CSSProperty.Display DISPLAY_TABLE_HEADER_GROUP = CSSProperty.Display.TABLE_HEADER_GROUP;
    public static final CSSProperty.Display DISPLAY_TABLE_FOOTER_GROUP = CSSProperty.Display.TABLE_FOOTER_GROUP;
    public static final CSSProperty.Display DISPLAY_TABLE_ROW = CSSProperty.Display.TABLE_ROW;
    public static final CSSProperty.Display DISPLAY_TABLE_COLUMN_GROUP = CSSProperty.Display.TABLE_COLUMN_GROUP;
    public static final CSSProperty.Display DISPLAY_TABLE_COLUMN = CSSProperty.Display.TABLE_COLUMN;
    public static final CSSProperty.Display DISPLAY_TABLE_CELL = CSSProperty.Display.TABLE_CELL;
    public static final CSSProperty.Display DISPLAY_TABLE_CAPTION = CSSProperty.Display.TABLE_CAPTION;
    
    public static final CSSProperty.Transform TRANSFORM_NONE = CSSProperty.Transform.NONE;
    
    public static final CSSProperty.Position POS_STATIC = CSSProperty.Position.STATIC;
    public static final CSSProperty.Position POS_RELATIVE = CSSProperty.Position.RELATIVE;
    public static final CSSProperty.Position POS_ABSOLUTE = CSSProperty.Position.ABSOLUTE;
    public static final CSSProperty.Position POS_FIXED = CSSProperty.Position.FIXED;
    
    public static final CSSProperty.WhiteSpace WHITESPACE_NORMAL = CSSProperty.WhiteSpace.NORMAL;
    public static final CSSProperty.WhiteSpace WHITESPACE_PRE = CSSProperty.WhiteSpace.PRE;
    public static final CSSProperty.WhiteSpace WHITESPACE_NOWRAP = CSSProperty.WhiteSpace.NOWRAP;
    public static final CSSProperty.WhiteSpace WHITESPACE_PRE_WRAP = CSSProperty.WhiteSpace.PRE_WRAP;
    public static final CSSProperty.WhiteSpace WHITESPACE_PRE_LINE = CSSProperty.WhiteSpace.PRE_LINE;
    
    /** Default line height if nothing or 'normal' is specified */
    private static final float DEFAULT_LINE_HEIGHT = 1.12f;
    
    /** Assigned element */
    protected Element el;

    /** First DOM child node index covered by this box (inclusive) */
    protected int firstDOMChild;
    
    /** First DOM child node index covered by this box (exclusive) */
    protected int lastDOMChild;
    
    /** Pre-created box to be added to this box before the DOM nodes are processed. Used during the box tree creation only. */
    protected Box preadd;
    
    /** Other boxes to be added to the tree after this one. Used during the box tree creation only. */
    protected Vector<Box> postadd;
    
    /** Current DOM child during the tree creation */
    protected BoxTreeCreationStatus curstat;
    
    /** Previous copy of the same box if the box has been split */
    protected ElementBox previousTwin;
    
    /** Next copy of the same box if the box has been split */
    protected ElementBox nextTwin;
    
    /** The style of the node (for element nodes only) with no pseudo classes */
    protected NodeData style;
    
    /** Efficient styles for the pseudo classes */
    protected Map<Selector.PseudoClassType, NodeData> pseudoStyle;
    
    /** Set to true when the element box contains only text boxes */
    protected boolean textonly;
    
    /** The map of related pseudo-elements (if any) */
    protected Map<Selector.PseudoElementType, ElementBox> pseudoElements;
    
    //============================== Computed style ======================
    
    /** The display property value */
    protected CSSProperty.Display display;
    
    /** Tansform property value (output transformations) */
    protected CSSProperty.Transform transform;
    
    /** Position property */
    protected CSSProperty.Position position;
    
    /** Position coordinates */
    protected LengthSet coords;
    
    /** The top position coordinate is set explicitely */
    protected boolean topset;
    
    /** The top position coordinate is set explicitely */
    protected boolean leftset;
    
    /** The top position coordinate is set explicitely */
    protected boolean bottomset;
    
    /** The top position coordinate is set explicitely */
    protected boolean rightset;
    
    /** The white-space property value */
    protected CSSProperty.WhiteSpace whitespace;
    
    /** Margin widths */
    protected LengthSet margin;
    
    /** Effective top and bottom margins (after collapsing with the contained boxes) */
    protected LengthSet emargin;
    
    /** Padding widths */
    protected LengthSet border;
    
    /** Border widths */
    protected LengthSet padding;
    
    /** Content sizes */
    protected Dimension content;
    
    /** Minimal absolute bounds. */
    protected Rectangle minAbsBounds;
    
    /** the computed value of line-height */
    protected float lineHeight;
    
    /** the z-index flag: true when z-index is different from <code>auto</code> */
    protected boolean zset;
    
    /** the z-index value when set */
    protected int zIndex;
    
    //============================== Child boxes ======================
    
    /** First valid child */
    protected int startChild;
    
    /** Last valid child (excl) */
    protected int endChild;

    /** A list of nested boxes (possibly empty). The box can contain either 
     * only block boxes or only inline boxes. The inline boxes can only
     * contain inline boxes */
    protected Vector<Box> nested;
    
    /** Corresponding stacking context if this box creates one. */
    protected StackingContext scontext;
    
    //=======================================================================
    
    /**
     * Creates a new element box from a DOM element
     * @param n the DOM element
     * @param g current graphics context
     * @param ctx current visual context
     */
    public ElementBox(Element n, VisualContext ctx)
    {
        super(n, ctx);
        minAbsBounds = null;
        style = null;
        pseudoStyle = new HashMap<>();
        if (n != null)
        {
	        el = n;
	        firstDOMChild = 0;
	        lastDOMChild = n.getChildNodes().getLength();
	        previousTwin = null;
	        nextTwin = null;
	        
	        nested = new Vector<Box>();
	        pseudoElements = new HashMap<>();
	        startChild = 0;
	        endChild = 0;
	        isblock = false;
	        textonly = true;
        }
        transform = TRANSFORM_NONE;
        position = POS_STATIC;
        topset = false;
        leftset = false;
        bottomset = false;
        rightset = false;
    }
    
    /**
     * Copy the values from another element box.
     * @param src source element box
     */ 
    public void copyValues(ElementBox src)
    {
        super.copyValues(src);
        nested.addAll(src.nested);
        textonly = src.textonly;
        pseudoElements = new HashMap<>(src.pseudoElements);
        style = src.style; 
        pseudoStyle = new HashMap<>(src.pseudoStyle);
        startChild = src.startChild;
        endChild = src.endChild;
        isblock = src.isblock;
        style = src.style;
        display = src.display;
        lineHeight = src.lineHeight;
        whitespace = src.whitespace;
        transform = src.transform;
        position = src.position;
        topset = src.topset;
        leftset = src.leftset;
        bottomset = src.bottomset;
        rightset = src.rightset;
        
        if (src.coords != null)
            coords = new LengthSet(src.coords);
        
        if (src.margin != null)
            margin = new LengthSet(src.margin);
        if (src.emargin != null)
            emargin = new LengthSet(src.emargin);
        if (src.border != null)
            border = new LengthSet(src.border);
        if (src.padding != null)
            padding = new LengthSet(src.padding);
        if (src.content != null)
            content = new Dimension(src.content);
    }
    
    /** Create a new box from the same DOM node in the same context */
    abstract public ElementBox copyBox();
    
    @Override
    public void initSubtree()
    {
        initBox();
        loadSizes();
        
        for (int i = 0; i < getSubBoxNumber(); i++)
            getSubBox(i).initSubtree();
        
        computeEfficientMargins();
    }
    
    //=======================================================================
    
    /**
     * @return the corresponding DOM element
     */
    public Element getElement()
    {
        return el;
    }
    
    /**
     * Indicates whether the backgound and borders should be rendered for this element.
     * This is true for normal elements but it is false e.g. for table row and body
     * elements
     * @return {@code true} when the background should be rendered
     */
    public boolean rendersBackground()
    {
        return true;
    }
    
    /**
     * Assign a new style to this box
     * @param s the new style declaration
     */
    public void setStyle(NodeData s)
    {
    	style = s;
    	loadBasicStyle();
    }
    
    /**
     * Returns the style of the DOM node that forms this box.
     * @return the style declaration
     */
    public NodeData getStyle()
    {
        return style;
    }
    
    /**
     * Obtains the string representation of the current style of the box in the CSS syntax.
     * @return The style string representation.
     */
    public String getStyleString()
    {
        if (style != null)
            return style.toString();
        else
            return "";
    }
    
    /**
     * Obtains the string value of the position: property
     * @return The string like "static", "absolute", "relative" or "fixed"
     */
    public String getPositionString()
    {
        return position.toString();
    }
    
    /**
     * Reads the string value of the specified property of the element style.
     * @param property the property name
     * @return the property value
     */
    public String getStylePropertyValue(String property)
    {
        Object t = style.getValue(Term.class, property);
        if (t == null)
            return "";
        else
            return t.toString();
    }
    
    /**
     * Reads a CSS length value of a style property of the box.
     * @param name property name
     * @return the length value
     */
    public TermLengthOrPercent getLengthValue(String name)
    {
        if (style != null)
            return style.getValue(TermLengthOrPercent.class, name);
        else
            return null;
    }
    
    /**
     * Reads the value of a border width specified by a CSS property.
     * @param dec a CSS decoder used for converting the values
     * @param property the property name, e.g. "border-top-width"
     * @return the border width in pixels
     */
    public float getBorderWidth(CSSDecoder dec, String property)
    {
        if (style != null)
        {
            CSSProperty.BorderWidth prop = style.getProperty(property);
            if (prop == CSSProperty.BorderWidth.length)
            {
                TermLengthOrPercent w = style.getValue(TermLengthOrPercent.class, property);
                if (w == null)
                    return CSSUnits.MEDIUM_BORDER;
                else
                {
                    float pxw = dec.getContext().pxLength(w);
                    if (pxw > 0 && pxw < 1)
                        return 1.0f; //thinner borders should be drawn with 1px line
                    else
                        return pxw;
                }
            }
            else
                return CSSUnits.convertBorderWidth(prop);
        }
        else
            return 0;
    }
    
    /**
     * Returns the value of the display property
     * @return One of the ElementBox.DISPLAY_XXX constants
     */
    public CSSProperty.Display getDisplay()
    {
    	return display;
    }
    
    public String getDisplayString()
    {
        if (display != null)
            return display.toString();
        else
            return "";
    }
    
    /**
     * Returns the value of the white-space property
     * @return one of the ElementBox.WHITESPACE_XXX constants
     */
    public CSSProperty.WhiteSpace getWhiteSpace()
    {
        return whitespace;
    }
    
    /**
     * Checks whether the whitespaces should be collapsed within in the element according to its style.
     * @return <code>true</code> if the whitespace sequences should be collapsed.
     */
    @Override
    public boolean collapsesSpaces()
    {
        return (whitespace != WHITESPACE_PRE && whitespace != WHITESPACE_PRE_WRAP);
    }
    
    /**
     * Checks whether this element should preserve the line breaks according to its style.
     * @return <code>true</code> when the line breaks should be preserved
     */
    @Override
    public boolean preservesLineBreaks()
    {
        return (whitespace != WHITESPACE_NORMAL && whitespace != WHITESPACE_NOWRAP);
    }
    
    /**
     * Checks whether this box allows line wrapping on whitespaces according to the whit-space setting.
     * @return <code>true</code> when line wrapping is allowed
     */
    @Override
    public boolean allowsWrapping()
    {
        return (whitespace == ElementBox.WHITESPACE_NORMAL
                || whitespace == ElementBox.WHITESPACE_PRE_WRAP
                || whitespace== ElementBox.WHITESPACE_PRE_LINE);
    }
    
    /**
     * Obtains the efficient background color to be used for drawing the background. This is a convenience
     * method for obtaining the color only. Other background properties such as images should
     * be obtained using {@link BackgroundDecoder}.
     * @return the background color or null when transparent
     */
    public Color getBgcolor()
    {
        CSSProperty.BackgroundColor bg = style.getProperty("background-color");
        if (bg == CSSProperty.BackgroundColor.color)
        {
            TermColor bgc = style.getSpecifiedValue(TermColor.class, "background-color");
            if (bgc.isTransparent())
                return null;
            else
                return bgc.getValue();
        }
        else
            return null;
    }
    
    /**
     * @return the number of subboxes in this box
     */
    public int getSubBoxNumber()
    {
        return nested.size();
    }

    /**
     * @return list of all the subboxes 
     */
    public List<Box> getSubBoxList()
    {
        return nested;
    }
    
    /**
     * @param index the sub box index in the range from 0 to n-1 
     * @return the appropriate sub box
     */
    public Box getSubBox(int index)
    {
        return nested.elementAt(index);
    }
    
    /**
     * Adds a new sub box to the end of the sub box list.
     * @param box the new sub box to add
     */
    public void addSubBox(Box box)
    {
        //find the last non-empty inline box to match white spaces
        Box last = null;
        int i = nested.size() - 1;
        while (i >= 0)
        {
            Box cand = nested.get(i);
            if (cand instanceof Inline && !((Inline) cand).collapsedCompletely())
            {
                last = cand;
                break;
            }
            i--;
        }
        //add the subbox
        box.setParent(this);
        nested.add(box);
        endChild++;
        if (isDisplayed() && !box.isEmpty())
            isempty = false;
        if (!(box instanceof TextBox))
            textonly = false;
        //collapse initiall white spaces when necessary
        if (last != null && last.collapsesSpaces() && last.endsWithWhitespace())
            box.setIgnoreInitialWhitespace(true);
    }
    
    /**
     * Removes a sub box from the subbox list
     * @param box the new sub box to add
     */
    public void removeSubBox(Box box)
    {
        if (nested.remove(box))
            endChild--;
    }
    
    /**
     * Removes all sub boxes from the subbox list
     */
    public void removeAllSubBoxes()
    {
        nested.removeAllElements();
        endChild = 0;
    }
    
    /**
     * Inserts a new sub box before a specified sub box
     * @param where the box already existing in the list
     * @param what the new box to add
     */
    public void insertSubBoxBefore(Box where, Box what)
    {
        int pos = nested.indexOf(where);
        nested.insertElementAt(what, pos);
        endChild++;
    }

    /**
     * Inserts a new sub box after a specified sub box
     * @param where the box already existing in the list
     * @param what the new box to add
     */
    public void insertSubBoxAfter(Box where, Box what)
    {
        int pos = nested.indexOf(where);
        nested.insertElementAt(what, pos+1);
        endChild++;
    }

    /**
     * Inserts a new sub box at a specified index
     * @param index the index where the new box will be placed
     * @param what the new box to add
     */
    public void insertSubBox(int index, Box what)
    {
        nested.insertElementAt(what, index);
        endChild++;
    }
    
    /**
     * Obtains the stacking context if this box creates one.
     */
    public StackingContext getStackingContext()
    {
        if (scontext == null)
        {
            if (formsStackingContext())
                scontext = new StackingContext(this);
            else
                log.warn("getStackingContext: obtaining a stacking context from element that does not create one");
        }
        return scontext;
    }
    
    /**
     * Sets related pseudo-element boxes
     * @param pseudo the name of the pseudo-element
     * @param box the corresponding pseudo-element box
     */
    public void setPseudoElement(Selector.PseudoElementType pseudo, ElementBox box)
    {
        pseudoElements.put(pseudo, box);
    }
    
    /**
     * Gets the list of related pseudo-element boxes
     * @param pseudo the name of the pseudo-element
     * @return the related box
     */
    public ElementBox getPseudoElement(Selector.PseudoElementType pseudo)
    {
        return pseudoElements.get(pseudo);
    }
    
    /**
     * Checks whether the element box has a related pseudo-element box
     * @param pseudo the name of the pseudo-element
     * @return the related box
     */
    public boolean hasPseudoElement(Selector.PseudoElementType pseudo)
    {
        return pseudoElements.containsKey(pseudo);
    }
    
    /**
     * @return the width of the content without any margins and borders
     */
    public float getContentWidth()
    {
    	return content.width;
    }
    
    /**
     * @return the height of the content without any margins and borders
     */
    public float getContentHeight()
    {
    	return content.height;
    }
    
    /**
     * Obtains the computed value of the declared line height of the element.
     * @return the line height in pixels
     */
    public float getLineHeight()
    {
        return lineHeight;
    }

    /**
     * @return the margin sizes
     */
    public LengthSet getMargin()
    {
        return margin;
    }
    
    /**
     * @return the effective margin sizes (after collapsing)
     */
    public LengthSet getEMargin()
    {
        return emargin;
    }
    
    /**
     * Obtains the position coordinates (top, left, bottom, right properties)
     * @return the set of coordinates
     */
    public LengthSet getCoords()
    {
        return coords;
    }
    
    /**
     * @return the border sizes (0 when no border is displayed)
     */
    public LengthSet getBorder()
    {
        return border;
    }
    
    /**
     * @return the padding sizes
     */
    public LengthSet getPadding()
    {
        return padding;
    }
    
    /**
     * @return the content sizes
     */
    public Dimension getContent()
    {
        return content;
    }
    
    /**
     * Checks whether the z-index is non-auto for this box.
     * @return <code>true</code> when z-index has a value different form <code>auto</code>.
     */
    public boolean hasZIndex()
    {
        return zset;
    }
    
    /**
     * Obtains the declared z-index for this element.
     * @return the z-index value
     */
    public int getZIndex()
    {
        return zIndex;
    }
    
    /**
     * Check whether the element forms a new stacking context.
     * @return <code>true</code> when the element creates a new stacking context.
     */
    public boolean formsStackingContext()
    {
        return position != POS_STATIC || transform != TRANSFORM_NONE;
    }
    
    /**
     * @return the first child from the list that is considered for rendering
     */
    public int getStartChild()
    {
        return startChild;
    }
    
    /**
     * @param index the index of the first child from the list that is considered for rendering
     */
    public void setStartChild(int index)
    {
        startChild = index;
    }
    
    /**
     * @return the last child from the list that is considered for rendering (not included)
     */
    public int getEndChild()
    {
        return endChild;
    }
    
    /**
     * @param index the index of the last child from the list that is considered for rendering
     */
    public void setEndChild(int index)
    {
        endChild = index;
    }
    
    /**
     * Sets the parent of the valid children to this (used while splitting the boxes)
     */
    public void adoptChildren()
    {
        for (int i = startChild; i < endChild; i++)
            nested.elementAt(i).setParent(this);
    }
    
    //=======================================================================

    /**
     * Computes the distance of the content from the left edge of the whole box
     * (a convenience function for margin + border + padding).
     * @return the distance 
     */
    public float getContentOffsetX()
    {
        return margin.left + border.left + padding.left;
    }
    
    /**
     * Computes the distance of the content from the top edge of the whole box. 
     * (a convenience function for margin + border + padding).
     * @return the distance
     */
    public float getContentOffsetY()
    {
        return emargin.top + border.top + padding.top;
    }
    
    public float getContentX()
    {
        return bounds.x + emargin.left + border.left + padding.left;
    }
    
    public float getContentY()
    {
        return bounds.y + emargin.top + border.top + padding.top;
    }
    
    public float getAbsoluteContentX()
    {
        return absbounds.x + emargin.left + border.left + padding.left;
    }
    
    public float getAbsoluteContentY()
    {
        return absbounds.y + emargin.top + border.top + padding.top;
    }
    
    public float totalWidth()
    {
        return emargin.left + border.left + padding.left + content.width +
            padding.right + border.right + emargin.right;
    }
    
    //totalHeight() differs for inline and block boxes
    
    public float getAvailableContentWidth()
    {
        return availwidth - emargin.left - border.left - padding.left 
                  - padding.right - border.right - emargin.right;
    }
    
    @Override
    public Rectangle getMinimalAbsoluteBounds()
    {
    	if (minAbsBounds == null)
    		minAbsBounds = computeMinimalAbsoluteBounds();
    	return minAbsBounds;
    }
    
    private Rectangle computeMinimalAbsoluteBounds()
    {
    	float rx1 = 0, ry1 = 0, rx2 = 0, ry2 = 0;
    	boolean valid = false;
    	for (int i = startChild; i < endChild; i++)
		{
			Box sub = getSubBox(i);
			Rectangle sb = sub.getMinimalAbsoluteBounds();
			if (sub.isDisplayed() && sub.isVisible() && sb.width > 0 && sb.height > 0)
			{
				if (sb.x < rx1 || !valid) rx1 = sb.x;
				if (sb.y < ry1 || !valid) ry1 = sb.y;
				if (sb.x + sb.width > rx2 || !valid) rx2 = sb.x + sb.width;
				if (sb.y + sb.height > ry2 || !valid) ry2 = sb.y + sb.height;
				valid = true;
			}
		}
    	return new Rectangle(rx1, ry1, rx2 - rx1, ry2 - ry1);
    }
    
    @Override
    public boolean affectsDisplay()
    {
        boolean ret = !isEmpty();
        //non-zero top or bottom border
        if (border.top > 0 || border.bottom > 0)
            ret = true;
        //the same with padding
        if (padding.top > 0 || padding.bottom > 0)
            ret = true;
        
        return ret;
    }
    
    @Override
    public boolean isVisible()
    {
        return visible && clipblock.isVisible() && clipblock.visibleInClip(this);
    }
    
    /**
     * Checks whether a box is visible in the content area of this box when this is a clipping block.
     * @param box The box to test.
     * @return {@code true} when at least a part of the given box is inside of our clipping area.
     */
    public boolean visibleInClip(Box box)
    {
        if (box instanceof ElementBox)
        {
            //the margin is never visible - use the border bounds instead of the full bounds
            return getClippedContentBounds().intersects(((ElementBox) box).getAbsoluteBorderBounds());
        }
        else
        {
            return getClippedContentBounds().intersects(box.getAbsoluteBounds());
        }
    }
    
    /**
     * Returns the bounds of the padding edge (the content and padding)
     * @return a Rectangle representing the absolute padding bounds
     */
    public Rectangle getPaddingBounds()
    {
        return new Rectangle(bounds.x + emargin.left + border.left,
                             bounds.y + emargin.top + border.top,
                             content.width + padding.left + padding.right,
                             content.height + padding.top + padding.bottom);
    }

    /**
     * Returns the absolute bounds of the padding edge (the content and padding)
     * @return a Rectangle representing the absolute padding bounds
     */
    public Rectangle getAbsolutePaddingBounds()
    {
        return new Rectangle(absbounds.x + emargin.left + border.left,
                             absbounds.y + emargin.top + border.top,
                             content.width + padding.left + padding.right,
                             content.height + padding.top + padding.bottom);
    }

    /**
     * Returns the bounds of the border edge (the content, padding and border)
     * @return a Rectangle representing the absolute border bounds
     */
    public Rectangle getAbsoluteBorderBounds()
    {
        return new Rectangle(absbounds.x + emargin.left,
                             absbounds.y + emargin.top,
                             content.width + padding.left + padding.right + border.left + border.right,
                             content.height + padding.top + padding.bottom + border.top + border.bottom);
    }
    
    /**
     * Computes the absolute clipping rectangle coordinates if this box is used as a clipping block.
     * @return the clipping rectangle coordinates
     */
    public Rectangle getClippedContentBounds()
    {
        if (clipblock == null)
            return getAbsolutePaddingBounds();
        else
            return clipblock.getClippedContentBounds().intersection(getAbsolutePaddingBounds());
    }
    
    @Override
    public String getText()
    {
        String ret = "";
        for (int i = startChild; i < endChild; i++)
        	ret += getSubBox(i).getText();
        return ret;
    }
    
    @Override
    public boolean isWhitespace()
    {
        for (int i = startChild; i < endChild; i++)
            if (!getSubBox(i).isWhitespace())
                return false;
        return true;
    }
        
    /**
     * Checks if the element contains only text boxes (no nested elements)
     * @return <code>true</code> when only text boxes are contained in this element
     */
    public boolean containsTextOnly()
    {
        for (Box child : nested)
            if (!(child instanceof TextBox))
                return false;
        return true;
    }
    
    /**
     * Checks if the element contains the mix of text boxes and elements
     * @return <code>true</code> when both text boxes and elements are directly contained in this element
     */
    public boolean containsMixedContent()
    {
        boolean ctext = false;
        boolean celem = false;
        for (Box child : nested)
        {
            if (child instanceof TextBox)
                ctext = true;
            else
                celem = true;
            
            if (ctext && celem)
                return true;
        }
        return false;
    }
    
    //=======================================================================
    
    /**
     * Draws the subtree as if this was a stacking context root.
     * @param include include new stacking contexts to this context (currently unused)
     */
    public void drawStackingContext(boolean include)
    {
        if (isDisplayed() && isDeclaredVisible())
        {
            Integer[] clevels = formsStackingContext() ? getStackingContext().getZIndices() : new Integer[0]; 
            
            //1.the background and borders of the element forming the stacking context.
            if (this.formsStackingContext() && this.rendersBackground())
                getViewport().getRenderer().renderElementBackground(this);
            
            getViewport().getRenderer().startElementContents(this);
            
            if (this.isReplaced() && this.formsStackingContext())
                getViewport().getRenderer().renderReplacedContent((ReplacedBox) this);
            
            //2.the child stacking contexts with negative stack levels (most negative first).
            int zi = 0;
            while (zi < clevels.length && clevels[zi] < 0)
            {
                drawChildContexts(clevels[zi]);
                zi++;
            }
            //3.the in-flow, non-inline-level, non-positioned descendants.
            drawChildren(DrawStage.DRAW_NONINLINE);
            //4.the non-positioned floats. 
            drawChildren(DrawStage.DRAW_FLOAT);
            //5.the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks. 
            drawChildren(DrawStage.DRAW_INLINE);
            //6.the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.
            if (zi < clevels.length && clevels[zi] == 0)
            {
                drawChildContexts(0);
                zi++;
            }
            //7.the child stacking contexts with positive stack levels (least positive first).
            while (zi < clevels.length)
            {
                drawChildContexts(clevels[zi]);
                zi++;
            }
            
            getViewport().getRenderer().finishElementContents(this);
            
        }
    }
    
    /**
     * Draws all the sub-boxes in the given stage.
     * @param turn The current drawing stage.
     */
    protected void drawChildren(DrawStage turn)
    {
        for (int i = startChild; i < endChild; i++)
        {
            Box subbox = getSubBox(i);
            subbox.draw(turn);
        }
    }
    
    /**
     * Draws child stacking contexts of the given z-index.
     * @param zindex The z-index of the contexts to draw. Only the child contexts with the given z-index will be drawn.
     */
    protected void drawChildContexts(int zindex)
    {
        Vector<ElementBox> list = getStackingContext().getElementsForZIndex(zindex);
        if (list != null)
        {
            for (ElementBox elem : list)
            {
                elem.drawStackingContext(!elem.hasZIndex());
            }
        }
    }
    
    //=======================================================================
    
    /**
     * @return <code> true when this box may contain block boxes (it may be a containing-block)
     */
    abstract public boolean mayContainBlocks();
    
    /**
     * Load the box sizes from the CSS properties.
     */
    abstract protected void loadSizes();
    
    /**
     * Update the box sizes according to the new parent size. Should be
     * called when the parent has been resized.
     */
    abstract public void updateSizes(); 
    
    /**
     * Re-calculates the sizes of all the child block boxes recursively.
     */
    public void updateChildSizes()
    {
        for (int i = 0; i < getSubBoxNumber(); i++)
        {
            Box child = getSubBox(i);
            if (child instanceof BlockBox)
            {
                //update the children only when some size has changed
                BlockBox block = (BlockBox) child;
                float oldw = block.getContentWidth();
                float oldh = block.getContentHeight();
                block.updateSizes();
                block.setSize(block.totalWidth(), block.totalHeight());
                if (block.getContentWidth() != oldw || block.getContentHeight() != oldh)
                {
                    block.updateChildSizes();
                }
            }
            else if (child instanceof ElementBox)
            {
                //always update the non-block elements
                ElementBox eb = (ElementBox) child;
                eb.updateSizes();
                eb.setSize(eb.totalWidth(), eb.totalHeight());
                eb.updateChildSizes();
            }
        }
    }
    
    /**
     * Computes the number of expansion points (generally spaces between words) that can be used
     * for extending the box width
     * @param start the first child to be processed
     * @param end the last child (the first one not to be processed)
     * @param atLineStart is the start box at the start of a line?
     * @param atLineEnd is the last box at the end of a line line?
     * @return the number of expansion points
     */
    protected int countInlineExpansionPoints(int start, int end, boolean atLineStart, boolean atLineEnd)
    {
        int cnt = 0;
        for (int i = start; i < end; i++)
        {
            final Box subbox = getSubBox(i);
            if (subbox instanceof Inline)
                cnt += ((Inline) subbox).getWidthExpansionPoints(atLineStart && (i == start), atLineEnd && (i == end - 1));
        }
        return cnt;
    }
    
    /**
     * Extends the total width of inline childs boxes by the given offset considering the 'expandability' of every child.
     * @param ofs the additional width in pixels (positive or negative)
     * @param start the first child to be processed
     * @param end the last child (the first one not to be processed)
     * @param atLineStart is the start box at the start of a line?
     * @param atLineEnd is the last box at the end of a line line?
     */
    protected void extendInlineChildWidths(float dif, int start, int end, boolean atLineStart, boolean atLineEnd)
    {
        //compute total width of child boxes
        final int total = countInlineExpansionPoints(start, end, atLineStart, atLineEnd);
        if (total > 0)
        {
            //distribute the offset among the children
            float ofsx = 0;
            float remain = dif;
            for (int i = start; i < end; i++)
            {
                final Box subbox = getSubBox(i);
                if (subbox instanceof Inline)
                {
                    final boolean lstart = atLineStart && (i == start);
                    final boolean lend = atLineEnd && (i == end - 1);
                    final int childexp = ((Inline) subbox).getWidthExpansionPoints(lstart, lend);
                    float toadd = dif * childexp / total;
                    if (toadd > remain)
                        toadd = remain;
                    subbox.moveRight(ofsx);
                    ((Inline) subbox).extendWidth(toadd, lstart, lend);
                    ofsx += toadd;
                    remain -= toadd;
                }
            }
        }
    }
    
    /**
     * Computes efficient top and bottom margins for collapsing.
     */
    abstract public void computeEfficientMargins();
    
    /**
     * Checks if the element's own top and bottom margins are adjoining 
     * according to the CSS specifiaction.
     * @return <code>true</code> if the margins are adjoining
     */
    abstract public boolean marginsAdjoin();
    
    /**
     * @return <code>true</code> if the box has a visible border around
     */
    protected boolean borderVisible(String dir)
    {
        CSSProperty.BorderStyle st = style.getProperty("border-"+dir+"-style");
        return (st != null && st != CSSProperty.BorderStyle.NONE  && st != CSSProperty.BorderStyle.HIDDEN); 
    }
    
    /**
     * Loads the border sizes from the style.
     * 
     * @param dec CSS decoder used for decoding the style
     * @param contw containing block width for decoding percentages
     */
    protected void loadBorders(CSSDecoder dec, float contw)
    {
        border = new LengthSet();
        if (borderVisible("top"))
            border.top = getBorderWidth(dec, "border-top-width");
        else
            border.top = 0;
        if (borderVisible("right"))
            border.right = getBorderWidth(dec, "border-right-width");
        else
            border.right = 0;
        if (borderVisible("bottom"))
            border.bottom = getBorderWidth(dec, "border-bottom-width");
        else
            border.bottom = 0;
        if (borderVisible("left"))
            border.left = getBorderWidth(dec, "border-left-width");
        else
            border.left = 0;
    }
    
    /**
     * Load the basic style from the CSS properties. This includes the display
     * properties, floating, positioning, color and font properties.
     */
    protected void loadBasicStyle()
    {
        // update the visual context based on the current style
        ctx.update(style);
        
        // decode additional style properties
        display = style.getProperty("display");
        if (display == null) display = CSSProperty.Display.INLINE;
        
        CSSProperty.Float floating = style.getProperty("float");
        if (floating == null) floating = BlockBox.FLOAT_NONE;
        
        position = style.getProperty("position");
        if (position == null) position = BlockBox.POS_STATIC;
        
        //apply combination rules
        //http://www.w3.org/TR/CSS21/visuren.html#dis-pos-flo
        if (display == ElementBox.DISPLAY_NONE)
        {
            position = BlockBox.POS_STATIC;
            floating = BlockBox.FLOAT_NONE;
        }
        else if (position == BlockBox.POS_ABSOLUTE || position == BlockBox.POS_FIXED)
        {
            floating = BlockBox.FLOAT_NONE;
        }
        //compute the display computed value
        if (floating != BlockBox.FLOAT_NONE || position == BlockBox.POS_ABSOLUTE || position == BlockBox.POS_FIXED || isRootElement())
        {
            if (display == DISPLAY_INLINE_TABLE)
                display = DISPLAY_TABLE;
            else if (display == DISPLAY_INLINE ||
                     display == DISPLAY_RUN_IN ||
                     display == DISPLAY_TABLE_ROW_GROUP ||
                     display == DISPLAY_TABLE_COLUMN ||
                     display == DISPLAY_TABLE_COLUMN_GROUP ||
                     display == DISPLAY_TABLE_HEADER_GROUP ||
                     display == DISPLAY_TABLE_FOOTER_GROUP ||
                     display == DISPLAY_TABLE_ROW ||
                     display == DISPLAY_TABLE_CELL ||
                     display == DISPLAY_TABLE_CAPTION ||
                     display == DISPLAY_INLINE_BLOCK)
                display = DISPLAY_BLOCK;
        }

        isblock = (display == DISPLAY_BLOCK);
        displayed = (display != DISPLAY_NONE && display != DISPLAY_TABLE_COLUMN);
        visible = (style.getProperty("visibility") != CSSProperty.Visibility.HIDDEN); 
        
        //line height
        CSSProperty.LineHeight lh = style.getProperty("line-height");
        if (lh == null || lh == CSSProperty.LineHeight.NORMAL)
            lineHeight = DEFAULT_LINE_HEIGHT * ctx.getFontHeight();
        else if (lh == CSSProperty.LineHeight.length)
        {
            TermLength len = style.getValue(TermLength.class, "line-height");
            lineHeight = ctx.pxLength(len);
        }
        else if (lh == CSSProperty.LineHeight.percentage)
        {
            TermPercent len = style.getValue(TermPercent.class, "line-height");
            lineHeight = ctx.pxLength(len, ctx.getFontHeight()); 
        }
        else //must be INTEGER or NUMBER
        {
            Term<?> len = style.getValue("line-height", true);
            float r;
            if (len instanceof TermInteger)
                r = ((TermInteger) len).getValue();
            else
                r = ((TermNumber) len).getValue();
            lineHeight = r * ctx.getFontHeight();
        }

        //whitespace
        whitespace = style.getProperty("white-space");
        if (whitespace == null) whitespace = WHITESPACE_NORMAL;
        
        //z-index
        CSSProperty.ZIndex z = style.getProperty("z-index");
        if (z != null && z != ZIndex.AUTO)
        {
            zset = true;
            Term<?> zterm = style.getValue("z-index", true);
            if (zterm instanceof TermInteger)
                zIndex = ((TermInteger) zterm).getValue().intValue();
            else
                zset = false;
        }
        else
            zset = false;
        
        //transformations -- applied on block-level or atomic inline-level elements only
        if (isBlock() || isReplaced())
            transform = style.getProperty("transform");
        if (transform == null) transform = CSSProperty.Transform.NONE;
    }
    
    /**
     * Loads the top, left, bottom and right coordinates from the style
     */
    protected void loadPosition()
    {
        CSSDecoder dec = new CSSDecoder(ctx);

        float contw = getContainingBlock().width;
        float conth = getContainingBlock().height;
        
        CSSProperty.Top ptop = style.getProperty("top");
        CSSProperty.Right pright = style.getProperty("right");
        CSSProperty.Bottom pbottom = style.getProperty("bottom");
        CSSProperty.Left pleft = style.getProperty("left");

        topset = !(ptop == null || ptop == CSSProperty.Top.AUTO);
        rightset = !(pright == null || pright == CSSProperty.Right.AUTO);
        bottomset = !(pbottom == null || pbottom == CSSProperty.Bottom.AUTO);
        leftset = !(pleft == null || pleft == CSSProperty.Left.AUTO);
        
        coords = new LengthSet();
        if (topset)
            coords.top = dec.getLength(getLengthValue("top"), (ptop == CSSProperty.Top.AUTO), 0, 0, conth);
        if (rightset)
            coords.right = dec.getLength(getLengthValue("right"), (pright == CSSProperty.Right.AUTO), 0, 0, contw);
        if (bottomset)
            coords.bottom = dec.getLength(getLengthValue("bottom"), (pbottom == CSSProperty.Bottom.AUTO), 0, 0, conth);
        if (leftset)
            coords.left = dec.getLength(getLengthValue("left"), (pleft == CSSProperty.Left.AUTO), 0, 0, contw);
    }
    
    /**
     * Updates the stacking parent values and registers the z-index for this parent.
     */
    @Override
    protected void updateStackingContexts()
    {
        super.updateStackingContexts();
        if (stackingParent != null)
        {
            if (formsStackingContext()) //all the positioned boxes are considered as separate stacking contexts
            {
                stackingParent.getStackingContext().registerChildContext(this);
                if (scontext != null) //clear this context if it exists (remove old children)
                    scontext.clear();
            }
        }
    }
    
} public interface Inline
{
    
    /**
     * Returns the declared line height.
     * @return The declared line height in pixels.
     */
    public float getLineHeight();
    
    
    /**
     * Returns the maximal declared line height of this box and its children.
     * @return Maximal declared line height in pixels.
     */
    public float getMaxLineHeight();

    /**
     * Obtains the distance from the line box top to the baseline of this box.
     * This corresponds to the total required space above the baseline of this box in the parent line box.
     * @return the <em>y</em> offset of the baseline in the parent line box
     */
    public float getBaselineOffset();
    
	/**
	 * Obtains the required height of the parent line box below the baseline.
	 * @return the total required space below the baseline in pixels
	 */
	public float getBelowBaseline();
	
	/**
	 * Obtains the total line box height obtained for this box and its
	 * subboxes during the layout. It consists of two parts: baselineOffset + belowBaseline.
	 * @return the total required height of the line in pixels
	 */
	public float getTotalLineHeight();
	
	public float getHalfLead();
	
    /**
     * Obtains the expected maximal length of the first line of this inline box.
     * This is useful when the line breaks are preserved. Otherwise, the length of the whole text is returned.
     * @return the length of the first line of the contents in pixels
     */
    public float getFirstLineLength();

    /**
     * Obtains the expected maximal length of the last line of this inline box.
     * This is useful when the line breaks are preserved. Otherwise, the length of the whole text is returned.
     * @return the length of the last line of the contents in pixels
     */
    public float getLastLineLength();
    
    /** 
     * Checks whether the inline box contains a line break. This may be only true when the line breaks are preserved
     * according to the whitespace style.
     * @return <code>true</code> when there is at least one preserved line break inside 
     */
    public boolean containsLineBreak();

    /**
     * Indicates whether the layout of this box content has been finished by a line break. 
     * @return <code>true</code> if the layout of this box has finished by a line break
     */
    public boolean finishedByLineBreak();
    
    /**
     * Indicates whether the box was completely collapsed to empty string during the laytout.
     * For example, it contains only whitespace characters, whitespace collapsing is applied
     * and the previous box already ends with a whitespace.
     * @return <code>true</code> when the box has been completely collapsed
     */
    public boolean collapsedCompletely();
    
    /**
     * Computes the number of expansion points (generally spaces between words) that can be used
     * for extending the box width
     * @param atLineStart is the box at the start of a line?
     * @param atLineEnd is the box at the end of a line line?
     * @return the number of expansion points
     */
    public int getWidthExpansionPoints(boolean atLineStart, boolean atLineEnd);
    
    /**
     * Extends the width of the box by the given offset
     * @param ofs the additional width in pixels (positive or negative)
     * @param atLineStart is the box at the start of a line?
     * @param atLineEnd is the box at the end of a line line?
     */
    public void extendWidth(float ofs, boolean atLineStart, boolean atLineEnd);
    
} public interface InlineElement extends Inline
{

    /**
     * Obtains the vertical alignment of the element as specified in the style.
     * @return a vertical alignment value
     */
    public CSSProperty.VerticalAlign getVerticalAlign();

    /**
     * Assigns the line box assigned to this inline box and all the inline sub-boxes.
     * @param linebox The assigned linebox.
     */
    public void setLineBox(LineBox linebox);
    
    /**
     * Returns the line box used for positioning this element.
     */
    public LineBox getLineBox();
    
    /**
     * Obtains the offset of the content edge from the line box top
     * @return the difference between the content edge and the top of the line box in pixels. Positive numbers mean the content box is inside the line box.  
     */
    public float getLineboxOffset();
    
} public class Rectangle
{
    public float x;
    public float y;
    public float width;
    public float height;
    
    public Rectangle(float x, float y, float width, float height)
    {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }

    public Rectangle(float width, float height)
    {
        this.x = 0;
        this.y = 0;
        this.width = width;
        this.height = height;
    }

    public Rectangle()
    {
        this.x = 0;
        this.y = 0;
        this.width = 0;
        this.height = 0;
    }
    
    public Rectangle(Rectangle src)
    {
        this.x = src.x;
        this.y = src.y;
        this.width = src.width;
        this.height = src.height;
    }
    
    public Rectangle(Dimension src)
    {
        this.x = 0;
        this.y = 0;
        this.width = src.width;
        this.height = src.height;
    }
    
    public float getX()
    {
        return x;
    }

    public void setX(float x)
    {
        this.x = x;
    }

    public float getY()
    {
        return y;
    }

    public void setY(float y)
    {
        this.y = y;
    }

    public float getWidth()
    {
        return width;
    }

    public void setWidth(float width)
    {
        this.width = width;
    }

    public float getHeight()
    {
        return height;
    }

    public void setHeight(float height)
    {
        this.height = height;
    }
    
    public void setLocation(float x, float y)
    {
        this.x = x;
        this.y = y;
    }
    
    public void setSize(float width, float height)
    {
        this.width = width;
        this.height = height;
    }
    
    public Dimension getSize()
    {
        return new Dimension(width, height);
    }
    
    public boolean contains(float x, float y)
    {
        if (width <= 0 || height <= 0)
            return false;
        return (x >= this.x && x < this.x + this.width &&
                y >= this.y && y < this.y + this.height);
    }
    
    public boolean intersects(Rectangle r)
    {
        float tw = this.width;
        float th = this.height;
        float rw = r.width;
        float rh = r.height;
        if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0) {
            return false;
        }
        float tx = this.x;
        float ty = this.y;
        float rx = r.x;
        float ry = r.y;
        rw += rx;
        rh += ry;
        tw += tx;
        th += ty;
        //      overflow || intersect
        return ((rw < rx || rw > tx) &&
                (rh < ry || rh > ty) &&
                (tw < tx || tw > rx) &&
                (th < ty || th > ry));
    }
    
    public Rectangle intersection(Rectangle r) 
    {
        float tx1 = this.x;
        float ty1 = this.y;
        float rx1 = r.x;
        float ry1 = r.y;
        float tx2 = tx1; tx2 += this.width;
        float ty2 = ty1; ty2 += this.height;
        float rx2 = rx1; rx2 += r.width;
        float ry2 = ry1; ry2 += r.height;
        if (tx1 < rx1) tx1 = rx1;
        if (ty1 < ry1) ty1 = ry1;
        if (tx2 > rx2) tx2 = rx2;
        if (ty2 > ry2) ty2 = ry2;
        tx2 -= tx1;
        ty2 -= ty1;
        return new Rectangle(tx1, ty1, tx2, ty2);
    }

    @Override
    public String toString()
    {
        return "Rectangle[x=" + x + ", y=" + y + ", width=" + width
                + ", height=" + height + "]";
    }
} public class TextBox extends Box implements Inline
{
    /** Assigned text node */
    private Text textNode;
    
    /** Text string after whitespace processing */
    private String text;
    
    /** The start index of the text substring to be displayed */
    private int textStart;

    /** The end index of the text substring to be displayed (excl) */
    private int textEnd;

    /** Maximal total width */
    private float maxwidth;
    
    /** Minimal total width */
    private float minwidth;
    
    /** Additional width required for justifying the text */
    private float expwidth;
    
    /** Expanding at the line end? */
    private boolean expAtLineEnd;
    
    /** Indicates whether to ignore initial whitespaces */
    private boolean ignoreinitialws;
    
    /** Indicates whether to collapse whitespaces at all */
    private boolean collapsews;
    
    /** Indicates whether it is allowed to split on whitespace */
    private boolean splitws;
    
    /** Indicatew whether line feeds should be treated as whitespace */
    private boolean linews;
    
    /** When line feeds are preserved, this contains the maximal length of the first line of the text. */
    private float firstLineLength;
    
    /** When line feeds are preserved, this contains the maximal length of the last line of the text. */
    private float lastLineLength;
    
    /** Contains the maximal length of the longest line of the text. */
    private float longestLineLength;
    
    /** Contains a preserved line break? */
    private boolean containsLineBreak;
    
    /** Layout finished with a line break? */
    private boolean lineBreakStop;
    
    /** Collapsed to an empty box? (e.g. whitespaces only) */
    private boolean collapsedCompletely;
    
    /** Used text transformation */
    private CSSProperty.TextTransform transform;
    
    /** Word spacing */
    private Float wordSpacing;
    
    
    //===================================================================
    
    /**
     * Creates a new TextBox formed by a DOM text node.
     * @param n the corresponding DOM text node
     * @param g the graphics context used for rendering
     * @param ctx current visual context
     */
    public TextBox(Text n, VisualContext ctx)
    {
        super(n, ctx);
        textNode = n;
        transform = TextTransform.NONE;
        wordSpacing = null;
        setWhiteSpace(ElementBox.WHITESPACE_NORMAL); //resets the text content and indices
        
        ignoreinitialws = false;
        collapsews = true;
        containsLineBreak = false;
        lineBreakStop = false;
        collapsedCompletely = false;
    }

    /**
     * Copy all the values from another text box.
     * @param src the source text box
     */
    public void copyValues(TextBox src)
    {
        super.copyValues(src);
        text = new String(src.text);
        ignoreinitialws = false; //only the first box should ignore
        collapsews = src.collapsews;
        splitws = src.splitws;
        linews = src.linews;
        firstLineLength = src.firstLineLength;
        lastLineLength = src.lastLineLength;
        longestLineLength = src.longestLineLength;
        containsLineBreak = src.containsLineBreak;
        transform = src.transform;
        wordSpacing = src.wordSpacing;
    }
    
    /** 
     * Create a new box from the same DOM node in the same context
     * @return the new TextBox 
     */
    public TextBox copyTextBox()
    {
        TextBox ret = new TextBox(textNode, ctx);
        ret.copyValues(this);
        return ret;
    }
    
    public String toString()
    {
        return "Text: " + text + "<" + textStart + "," + textEnd + ">";
    }
    
    @Override
    public void initBox()
    {
    }
    
    @Override
    public void setParent(ElementBox parent)
    {
        super.setParent(parent);
        if (getParent() != null)
        {
            //load the relevant style values
            transform = getParent().getStyle().getProperty("text-transform");
            if (transform == null)
                transform = TextTransform.NONE;
            CSSProperty.WordSpacing wspacing = getParent().getStyle().getProperty("word-spacing");
            if (wspacing != null && wspacing != WordSpacing.NORMAL)
            {
                TermLength lenspec = getParent().getStyle().getValue(TermLength.class, "word-spacing");
                if (lenspec != null)
                    wordSpacing = ctx.pxLength(lenspec);            
            }
            else
                wordSpacing = null;
            //reset the whitespace processing according to the parent settings
            CSSProperty.WhiteSpace ws = getParent().getWhiteSpace();
            if (ws != ElementBox.WHITESPACE_NORMAL || transform != TextTransform.NONE)
                setWhiteSpace(ws);
        }
    }

    /**
     * @return the text contained in this box
     */
    @Override
    public String getText()
    {
        if (text != null)
            return text.substring(textStart, textEnd);
        else
            return "";
    }

    @Override
    public boolean isDeclaredVisible()
    {
        return true;
    }

    @Override
    public boolean isDisplayed()
    {
        if (getParent() == null)
            return true;
        else
            return parent.isDisplayed();
    }

    @Override
    public boolean isVisible()
    {
        if (getParent() == null)
            return true;
        else
            return parent.isDeclaredVisible() && super.isVisible();
    }
    
    //=======================================================================

    /**
     * Sets the whitespace processing for this box. The text content is then treated accordingly. The text start and text end
     * indices are reset to their initial values.
     */
    public void setWhiteSpace(CSSProperty.WhiteSpace value)
    {
        splitws = (value == ElementBox.WHITESPACE_NORMAL || value == ElementBox.WHITESPACE_PRE_WRAP || value== ElementBox.WHITESPACE_PRE_LINE);
        collapsews = (value == ElementBox.WHITESPACE_NORMAL || value == ElementBox.WHITESPACE_NOWRAP || value == ElementBox.WHITESPACE_PRE_LINE);
        linews = (value == ElementBox.WHITESPACE_NORMAL || value == ElementBox.WHITESPACE_NOWRAP);
        //When this is the original box, apply the whitespace. For the copied boxes, the whitespace has been already applied (they contain
        //a copy of the original, already processed content). 
        if (!splitted)
            applyWhiteSpace();
        //recompute widths (possibly different wrapping)
        computeLineLengths();
        minwidth = computeMinimalWidth();
        maxwidth = computeMaximalWidth();
    }
    
    /**
     * Applies the whitespace processing represented by the {@link #collapsews} property to the text content. The text start and text end
     * indices are reset to their initial values.
     */
    private void applyWhiteSpace()
    {
        text = applyTransformations(collapseWhitespaces(node.getNodeValue()));
        textStart = 0;
        textEnd = text.length();
        isempty = (textEnd == 0);
    }
    
    /**
     * Applies the whitespace removal rules used in HTML.
     * @param src source string
     * @return a new string with additional whitespaces removed
     */
    private String collapseWhitespaces(String src)
    {
        StringBuffer ret = new StringBuffer();
        boolean inws = false;
        for (int i = 0; i < src.length(); i++)
        {
            char ch = src.charAt(i);
            if (collapsews && isWhitespace(ch))
            {
                if (!inws)
                {
                    ret.append(' ');
                    inws = true;
                }
            }
            else if (isLineBreak(ch))
            {
            	ret.append('\r'); //represent all line breaks as LF
            	//reduce eventual CR+LF to CR only
            	if (ch == '\r' && i+1 < src.length() && src.charAt(i+1) == '\n')
            		i++;
            }
            else
            {
                inws = false;
                ret.append(ch);
            }
        }
        return new String(ret);
    }

    /**
     * Applies the text transformations to a string according to the current style.
     * @param src The source string
     * @return the string after transformations 
     */
    private String applyTransformations(String src)
    {
        switch (transform)
        {
            case LOWERCASE:
                return src.toLowerCase();
            case UPPERCASE:
                return src.toUpperCase();
            case CAPITALIZE:
                StringBuilder ret = new StringBuilder(src.length());
                boolean ws = true;
                for (int i = 0; i < src.length(); i++)
                {
                    char ch = src.charAt(i);
                    if (Character.isWhitespace(ch))
                        ws = true;
                    else
                    {
                        if (ws)
                            ch = Character.toUpperCase(ch);
                        ws = false;
                    }
                    ret.append(ch);
                }
                return ret.toString();
            default:
                return src;
        }
    }
    
    /**
     * Removes the trailing whitespaces from the cotnained text string. This must be done before the layout (it resets the text start and end pointers). 
     */
    public void removeTrailingWhitespaces()
    {
        int last = -1;
        for (int i = text.length() - 1; i >= 0; i--)
        {
            if (isWhitespace(text.charAt(i)))
                last = i;
            else
                break;
        }
        if (last != -1)
        {
            text = text.substring(0, last);
            textStart = 0;
            textEnd = last;
        }
    }
    
    /**
     * @return the start offset in the text string
     */ 
    protected int getTextStart()
    {
        return textStart;
    }
    
    /**
     * @param index the start offset in the text string
     */ 
    protected void setTextStart(int index)
    {
        textStart = index;
    }
    
    /**
     * @return the end offset in the text string (not included)
     */ 
    protected int getTextEnd()
    {
        return textEnd;
    }
    
    /**
     * @param index the end offset in the text string (not included)
     */ 
    protected void setTextEnd(int index)
    {
        textEnd = index;
    }
    
	@Override
    public boolean affectsDisplay()
    {
        return !isEmpty();
    }
    
	@Override
    public boolean isWhitespace()
    {
		//after wihtespace processing, all whitespaces should be represented by ' '
		String s = getText();
		for (int i = 0; i < s.length(); i++)
			if (s.charAt(i) != ' ')
				return false;
		return true;
    }
    
    @Override
    public boolean collapsesSpaces()
    {
        return collapsews;
    }
    
    @Override
    public boolean preservesLineBreaks()
    {
        return !linews;
    }

    @Override
    public boolean allowsWrapping()
    {
        return splitws;
    }

    @Override
    public float getContentX() 
    {
        return bounds.x;
    }
    
	@Override
    public float getAbsoluteContentX() 
    {
        return absbounds.x;
    }
    
	@Override
    public float getContentY() 
    {
        return bounds.y;
    }

	@Override
    public float getAbsoluteContentY() 
    {
        return absbounds.y;
    }

	@Override
    public float getContentWidth() 
    {
        return bounds.width;
    }
    
	@Override
    public float getContentHeight() 
    {
        return bounds.height;
    }

	@Override
    public float getAvailableContentWidth() 
    {
        return availwidth;
    }
    
    public float getLineHeight()
    {
        return parent.getLineHeight();
    }
    
    public float getMaxLineHeight()
    {
        return parent.getLineHeight();
    }
    
    public float getTotalLineHeight()
    {
        return ctx.getFontHeight();
    }

    public float getBaselineOffset()
    {
        return ctx.getBaselineOffset();
    }
    
    public float getBelowBaseline()
    {
        return ctx.getFontHeight() - ctx.getBaselineOffset();
    }
    
    public float getHalfLead()
    {
        return 0;
    }
    
    @Override
    public float totalHeight() 
    {
        return bounds.width;
    }
    
	@Override
    public float totalWidth() 
    {
        return bounds.height;
    }
    
	@Override
    public Rectangle getMinimalAbsoluteBounds()
    {
    	return absbounds;
    }
    
	@Override
    public boolean isInFlow()
	{
		return true;
	}
	
	@Override
	public boolean containsFlow()
	{
		return false;
	}
	
    @Override
    public boolean canSplitInside()
    {
        return (getText().indexOf(' ') != -1);
    }
    
    @Override
    public boolean canSplitBefore()
    {
        if (textEnd > textStart)
        	return (text.charAt(textStart) == ' ' ||
        			(textStart > 0 && text.charAt(textStart-1) == ' '));
        else
        	return false;
    }
    
    @Override
    public boolean canSplitAfter()
    {
        if (textEnd > textStart)
	        return (text.charAt(textEnd-1) == ' ' ||
	                (textEnd < text.length() && text.charAt(textEnd) == ' '));
        else
        	return false;
    }
    
    @Override
    public boolean startsWithWhitespace()
    {
        if (textEnd > textStart)
            return (Character.isWhitespace(text.charAt(textStart)));
        else
            return false;
    }
    
    @Override
    public boolean endsWithWhitespace()
    {
        if (textEnd > textStart)
            return (Character.isWhitespace(text.charAt(textEnd-1)));
        else
            return false;
    }
    
    @Override
    public void setIgnoreInitialWhitespace(boolean b)
    {
        this.ignoreinitialws = b;
        //collapse spaces when necessary
        if (ignoreinitialws && collapsews)
        {
            while (textStart < textEnd && isWhitespace(text.charAt(textStart)))
                textStart++;
            if (textStart == textEnd)
                collapsedCompletely = true;
            //recompute widths (possibly different wrapping)
            computeLineLengths();
            minwidth = computeMinimalWidth();
            maxwidth = computeMaximalWidth();
        }
    }
    
	@Override
	public boolean hasFixedWidth()
	{
		return false; //the width depends on the content
	}

	@Override
	public boolean hasFixedHeight()
	{
		return false;
	}

    //=======================================================================
    
	/** 
	 * Compute the width and height of this element. Layout the sub-elements.
     * @param widthlimit Maximal width available for the element
     * @param force Use the area even if the used width is greater than maxwidth
     * @param linestart Indicates whether the element is placed at the line start
     * @return <code>true</code> if the box has been succesfully placed
     */
	@Override
    public boolean doLayout(float widthlimit, boolean force, boolean linestart)
    {
        //Skip if not displayed
        if (!displayed)
        {
            bounds.setSize(0, 0);
            return true;
        }
        
        //reset textEnd if we are doing a new layout
        if (!splitted)
            textEnd = text.length();
        
        setAvailableWidth(widthlimit);
        
        boolean split = false; //should we split to more boxes?
        boolean allow = false; //allow succesfull result even if nothing has been placed (line break only)
        boolean fail = false; //failed totally (nothing fit)
        float wlimit = getAvailableContentWidth();
        boolean empty = isempty;
        float w = 0, h = 0;
        
        int end = textEnd;
        int lineend = text.indexOf('\r', textStart);
        if (lineend != -1 && lineend < end) //preserved end-of-line encountered
        {
        	end = lineend; //split at line end (or earlier)
        	split = true;
        	allow = true;
        }
        
        if (!empty || !linestart) //ignore empty text elements at the begining of a line
        {
            //ignore spaces at the begining of a line
            if ((linestart || ignoreinitialws) && collapsews)
            {
                while (textStart < end && isWhitespace(text.charAt(textStart)))
                    textStart++;
                if (textStart == end)
                {
                    collapsedCompletely = true;
                    empty = true; //collapsed to an empty box
                }
            }
            //try to place the text
            do
            {
                w = stringWidth(text.substring(textStart, end));
                h = ctx.getFontHeight();
                if (w > wlimit) //exceeded - try to split if allowed
                {
                    if (empty) //empty or just spaces - don't place at all
                    {
                        w = 0; h = 0;
                        split = false;
                        break;
                    }
                    int wordend = text.substring(0, end).lastIndexOf(' '); //find previous word
                    while (wordend > 0 && text.charAt(wordend-1) == ' ') wordend--; //skip trailing spaces
                    if (wordend <= textStart || !splitws) //no previous word, cannot split or splitting not allowed
                    {
                        if (!force) //everything failed
                        {
                        	//System.out.println("Here for " + this);
                            end = textEnd; //we will try with the whole rest next time
                            split = false; 
                            allow = false; //split before the linebreak
                            fail = true;
                        }
                        else
                            split = true;
                        break;
                    }
                    else
                    {
                        end = wordend;
                        split = true;
                    }
                }
            } while (end > textStart && w > wlimit);
        }
        textEnd = end;
        bounds.setSize(w, h);
        
        //if not the whole element was placed, create the rest
        if (split)
        {
            //skip the eventual line break
            int start = textEnd;
            if (start < text.length() && isLineBreak(text.charAt(start)))
            {
                start++;
                lineBreakStop = true;
            }
            //find the start of the next word
            if (collapsews)
            {
                while (start < text.length() && isWhitespace(text.charAt(start)))
                    start++;
            }
            //create the rest if something has left
            if (start < text.length())
            {
                TextBox rtext = copyTextBox();
                rtext.splitted = true;
                rtext.splitid = splitid + 1;
                rtext.setTextStart(start);
                rtext.setTextEnd(text.length());
                rest = rtext;
            }
            else
                rest = null;
        }
        else
            rest = null;
        
        return !fail && ((textEnd > textStart) || empty || allow);
    }
    
	@Override
    public void absolutePositions()
    {
	    updateStackingContexts();
        if (displayed)
        {
            //my top left corner
            absbounds.x = getParent().getAbsoluteContentX() + bounds.x;
            absbounds.y = getParent().getAbsoluteContentY() + bounds.y;
	        absbounds.width = bounds.width;
	        absbounds.height = bounds.height;
        }
    }
    
	@Override
    public float getMinimalWidth()
    {
		return minwidth;
    }
	
    private float computeMinimalWidth()
    {
        float ret = 0;
        String t = getText();
        if (t.length() > 0)
        {
            if (splitws)
            {
                //wrapping allowed - returns the length of the longest word
                ret = getLongestWord();
            }
            else
            {
            	//cannot wrap - return the width of the whole string
                ret = longestLineLength;
            }
        }
        return ret;
    }
    
	@Override
    public float getMaximalWidth()
    {
		return maxwidth;
    }
	
    private float computeMaximalWidth()
    {
        if (linews)
        {
            //no preserved line breaks -- returns the lenth of the whole string
            float len = stringWidth(getText());
            firstLineLength = len;
            lastLineLength = len;
            longestLineLength = len;
            return len;
        }
        else
        {
            return longestLineLength;
        }
    }
    
    private float getLongestWord()
    {
        float ret = 0;
        String t = getText();
        
        int s1 = 0;
        int s2 = t.indexOf(' ');
        do
        {
            if (s2 == -1) s2 = t.length();
            float w = stringWidth(t.substring(s1, s2));
            if (w > ret) ret = w;
            s1 = s2 + 1;
            s2 = t.indexOf(' ', s1);
        } while (s1 < t.length() && s2 < t.length());
        
        return ret;
    }

    @Override
    public float getFirstLineLength()
    {
        return firstLineLength;
    }

    @Override
    public float getLastLineLength()
    {
        return lastLineLength;
    }

    @Override
    public boolean containsLineBreak()
    {
        return containsLineBreak;
    }
    
    @Override
    public boolean finishedByLineBreak()
    {
        return lineBreakStop;
    }

    @Override
    public boolean collapsedCompletely()
    {
        return collapsedCompletely;
    }
    
    @Override
    public int getWidthExpansionPoints(boolean atLineStart, boolean atLineEnd)
    {
        if (collapsews && splitws && !lineBreakStop)
        {
            int cnt = 0;
            final String text = getText();
            for (int i = 0; i < text.length(); i++)
            {
                if (text.charAt(i) == ' '
                        && !(i == 0 && atLineStart) //do not consider the initial space if at line beginning 
                        && !(i == text.length() - 1 && atLineEnd)) //do not consider the trailing space if at line end
                    cnt++;
            }
            return cnt;
        }
        else
            return 0; //spaces preserved or wrapping not allowed - cannot expand
    }

    @Override
    public void extendWidth(float ofs, boolean atLineStart, boolean atLineEnd)
    {
        expwidth = ofs;
        expAtLineEnd = atLineEnd;
        bounds.width += ofs;
    }
    
    /**
     * The extra box width used for justifying the contents. The rendered width should be extended
     * by this amount in total.
     * @return The extra width or 0 when no extra width is required
     */
    public float getExtraWidth()
    {
        return expwidth;
    }

    /**
     * The additional word spacing in pixels.
     * @return the additional word spacing in pixels or <code>null</code> when normal word spacing is used
     */
    public Float getWordSpacing()
    {
        return wordSpacing;
    }

    /**
     * Computes the X offsets of the individual words
     * @param words the words
     * @return the array n elements where n is the number of words. The a[i][0] contains the
     * offset of the i-th words, a[i][1] contains the width of the word in pixels.
     */
    public float[][] getWordOffsets(String[] words)
    {
        if (words.length > 0)
        {
            //determine word lengths
            float[] ww = new float[words.length];
            float totalw = 0;
            for (int i = 0; i < words.length; i++)
            {
                ww[i] = stringWidth(words[i]);
                totalw += ww[i];
            }
            //spacing
            int spaces = words.length - 1;
            if (startsWithWhitespace())
                spaces++;
            if (endsWithWhitespace() && !expAtLineEnd)
                spaces++;
            final float spacing = (spaces == 0) ? (bounds.width - totalw) : (bounds.width - totalw) / (float) spaces;
            //layout
            float[][] ret = new float[words.length][2];
            float curX = 0;
            if (startsWithWhitespace())
                curX += spacing;
            for (int i = 0; i < words.length; i++)
            {
                ret[i][0] = curX;
                ret[i][1] = ww[i];
                curX += ww[i] + spacing;            
            }
            return ret;
        }
        else
            return new float[0][0];
    }
    
    /**
     * Computes the X pixel offset of the given character of the box text. The character position is specified relatively
     * to the box text.
     * @param pos the character position in the string relative to the start of the box (0 is the first character in the box)
     * @return the X offset in pixels
     */
    public float getCharOffsetX(int pos)
    {
        return getCharOffsetXElem(pos + textStart);
    }
    
    /**
     * Computes the X pixel offset of the given character of the box text. The character position is specified within the
     * whole source text node.
     * @param pos the character position in the string absolutely within the source text node (0 is the first character in the node)
     * @return the X offset in pixels or 0 when the position does not fit to this box
     */
    public float getCharOffsetXElem(int pos)
    {
        if (text != null)
        {
            if (pos <= textStart)
                return 0;
            else if (pos > textStart && pos < textEnd)
                return stringWidth(text.substring(textStart, pos));
            else
                return stringWidth(text.substring(textStart, textEnd));
        }
        else
            return 0;
    }
    
    /**
     * Computes the lengths of the first, last and longest lines.
     */
    protected void computeLineLengths()
    {
        firstLineLength = -1;
        lastLineLength = 0;
        longestLineLength = 0;
        
        String t = getText();
        
        int s1 = 0;
        int s2 = t.indexOf('\r');
        float w = 0;
        do
        {
            if (s2 == -1)
                s2 = t.length();
            else
                containsLineBreak = true;
            w = stringWidth(t.substring(s1, s2));
            if (firstLineLength == -1) firstLineLength = w;
            if (w > longestLineLength) longestLineLength = w;
            s1 = s2 + 1;
            s2 = t.indexOf('\r', s1);
        } while (s1 < t.length() && s2 < t.length());
        lastLineLength = w;
    }

    /**
     * Computes the final width of a string while considering word-spacing
     * @param fm the font metrics used for calculation
     * @param text the string to be measured
     * @return the resulting width in pixels
     */
    private float stringWidth(String text)
    {
        float w = ctx.stringWidth(text);
        if (wordSpacing != null)
        {
            //count spaces and add
            float add = 0.0f;
            for (int i = 0; i < text.length(); i++)
            {
                if (text.charAt(i) == ' ')
                    add += wordSpacing;
            }
            w += add;
        }
        return w;
    }
    
	@Override
    public void draw(DrawStage turn)
    {
        if (displayed && isVisible())
        {
            if (turn == DrawStage.DRAW_INLINE)
            {
                getViewport().getRenderer().renderTextContent(this);
            }
        }
    }
	
    /**
     * Computes efficient text decoration for the text box including the decoration
     * propagated from the parent boxes as defined in the 
     * <a href="https://www.w3.org/TR/CSS22/text.html#propdef-text-decoration">CSS specification</a>.
     * @return A set of text decorations for the box.
     */
	public Set<CSSProperty.TextDecoration> getEfficientTextDecoration()
    {
        final Set<CSSProperty.TextDecoration> ret = new HashSet<>();
        Box curbox = this;
        ret.addAll(curbox.getVisualContext().getTextDecoration());
        while (curbox.getParent() != null && acceptsPropagatedDecorations(curbox))
        {
            curbox = curbox.getParent();
            ret.addAll(curbox.getVisualContext().getTextDecoration());
        }
        return ret;
    }
    
	//===============================================================================
	
	/**
	 * Checks if a character can be interpreted as whitespace according to current settings.
	 * @param ch the character
	 * @return <code>true</code> when <code>ch</code> is a whitespace character
	 */
    private boolean isWhitespace(char ch)
    {
        if (linews) 
            return Character.isWhitespace(ch);
        else
            return ch != '\n' && ch != '\r' && Character.isWhitespace(ch);
    }
    
    /**
     * Checks if a character can be interpreted as a line break according to current settings.
     * @param ch the character
     * @return <code>true</code> when <code>ch</code> is the line break
     */
    private boolean isLineBreak(char ch)
    {
        if (linews)
            return false;
        else
            return (ch == '\r' || ch == '\n');
    }
    
    /**
     * Checks whether the box accepts the text decorations propagated from the parent boxes.
     * @param box the box to test
     * @return {@code true} when the text decorations should be propagated to the given box.
     */
    private boolean acceptsPropagatedDecorations(Box box)
    {
        return !(box instanceof BlockBox 
                && (((BlockBox) box).isFloating() || ((BlockBox) box).isPositioned() || box instanceof Inline));
    }

} public class Viewport extends BlockBox
{
    private static Logger log = LoggerFactory.getLogger(Viewport.class);
    
    /** Total canvas width */
	private float width;
	/** Total canvas height */
	private float height;
	/** Visible rectagle -- the position and size of the CSS viewport */
    private Rectangle visibleRect;
	
    protected BrowserConfig config;
	private BoxFactory factory;
	private BoxRenderer renderer;
	private Element root; //the DOM root
	private ElementBox rootBox; //the box that corresponds to the root node. It should be one of the child boxes.
    private boolean rootOverflowVisible = true; //has the root box originally had overflow:visible?
    private float maxx; //maximal X position of all the content
    private float maxy; //maximal Y position of all the content
    private boolean recomputeAbs; //indicates that the absolute positions need to be recomputed
    
    /**
     * Creates a new Viewport with the given initial size. The actual size may be increased during the layout. 
     *  
     * @param e The anonymous element representing the viewport.
     * @param ctx
     * @param factory The factory used for creating the child boxes.
     * @param root The root element of the rendered document.
     * @param width Preferred (minimal) width.
     * @param height Preferred (minimal) height.
     */
    public Viewport(Element e, VisualContext ctx, BoxFactory factory, Element root, float width, float height)
	{
		super(e, ctx);
		ctx.setViewport(this);
		this.factory = factory;
		this.root = root;
		style = CSSFactory.createNodeData(); //Viewport starts with an empty style
        nested = new Vector<Box>();
        startChild = 0;
        endChild = 0;
		this.width = width;
		this.height = height;
        isblock = true;
        contblock = true;
        root = null;
        visibleRect = new Rectangle(0, 0, width, height);
	}
    
    /**
     * Obtains the position of the visible part (CSS viewport) in the canvas.
     * @return the visible rectangle
     */
    public Rectangle getVisibleRect()
    {
        return visibleRect;
    }

    /**
     * Sets the position of the visible part (CSS viewport) in the canvas.
     * @param visibleRect the visible rectangle to be set
     */
    public void setVisibleRect(Rectangle visibleRect)
    {
        this.visibleRect = visibleRect;
        this.content = visibleRect.getSize();
    }

    /**
     * Obtains the width of the whole canvas that represents the whole rendered page.
     * @return The canvas size.
     */
    public float getCanvasWidth()
    {
        return width;
    }
    
    /**
     * Obtains the height of the whole canvas that represents the whole rendered page.
     * @return The canvas size.
     */
    public float getCanvasHeight()
    {
        return height;
    }
    
    /**
     * Obtains the current browser configuration.
     * @return current configuration.
     */
    public BrowserConfig getConfig()
    {
        return config;
    }

    /**
     * Sets the browser configuration used for rendering.
     * @param config the new configuration.
     */
    public void setConfig(BrowserConfig config)
    {
        this.config = config;
        overflowX = overflowY = config.getClipViewport() ? OVERFLOW_HIDDEN : OVERFLOW_VISIBLE;
    }
    
    @Override
    public void initSubtree()
    {
        super.initSubtree();
    }
    
    @Override
    public String toString()
    {
        return "Viewport " + width + "x" + height + 
                "[visible " + visibleRect.x + "," +visibleRect.y + "," +
                visibleRect.width + "," + visibleRect.height + "]"; 
    }
    
    public BoxFactory getFactory()
    {
        return factory;
    }

    public float getMinimalWidthLimit()
    {
    	return width;
    }
    
    /**
     * Obtains the DOM root element.
     * @return The root element of the document.
     */
    public Element getRootElement()
    {
        return root;
    }

    /**
     * Obtains the child box the corresponds to the DOM root element.
     * @return the corresponding element box or <code>null</code> if the viewport is empty.
     */
    public ElementBox getRootBox()
    {
        return rootBox;
    }
    
    public ElementBox getElementBoxByName(String name, boolean case_sensitive)
    {
        return recursiveFindElementBoxByName(this, name, case_sensitive);
    }
    
	@Override
    public void addSubBox(Box box)
    {
        super.addSubBox(box);
        if (box instanceof ElementBox && ((ElementBox) box).getElement() == root)
        {
            if (rootBox != null)
                log.debug("Viewport warning: another root box '" + box + "' in addition to previous '" + rootBox + "'");
            box.makeRoot();
            rootBox = (ElementBox) box;
        }
    }

    @Override
	public boolean hasFixedHeight()
	{
		return true;
	}

	@Override
	public boolean hasFixedWidth()
	{
		return true;
	}

	@Override
    public boolean canIncreaseWidth()
    {
        return true;
    }

    @Override
    public boolean isVisible()
    {
        return true;
    }

    @Override
    public boolean visibleInClip(Box box)
    {
        if (config.getClipViewport())
            return super.visibleInClip(box);
        else
        {
            //not clipping - everything that is in positive coordinates is visible in viewport
            Rectangle bb;
            if (box instanceof ElementBox)
                bb = ((ElementBox) box).getAbsoluteBorderBounds();
            else
                bb = box.getAbsoluteBounds();
            return (bb.x + bb.width > 0) && (bb.y + bb.height > 0);
        }
    }
    
    @Override
    protected boolean separatedFromTop(ElementBox box)
    {
        return true;
    }

    @Override
    protected boolean separatedFromBottom(ElementBox box)
    {
        return true;
    }

	@Override
	public void setSize(float width, float height)
    {
        this.width = width;
        this.height = height;
        content = new Dimension(width, height);
        bounds = new Rectangle(0, 0, totalWidth(), totalHeight());
    }
    
	@Override
	public Viewport getViewport()
	{
		return this;
	}

    @Override
    protected void loadPosition()
    {
        position = BlockBox.POS_ABSOLUTE;
        topset = true;
        leftset = true;
        bottomset = false;
        rightset = false;
        coords = new LengthSet(0, 0, 0, 0);
    }
    
	@Override
	protected void loadSizes(boolean update)
	{
		if (!update)
		{
			margin = new LengthSet();
			emargin = new LengthSet();
			declMargin = new LengthSet();
			border = new LengthSet();
			padding = new LengthSet();
			content = new Dimension(width, height);
			min_size = new Dimension(-1, -1);
			max_size = new Dimension(-1, -1);
			loadPosition();
		}
		bounds = new Rectangle(0, 0, totalWidth(), totalHeight());
	}

	@Override
    public void setContentWidth(float width)
    {
	    //do not descrease the viewport width under the initial value
        if (width > this.width)
            super.setContentWidth(width);
    }

    @Override
    public void setContentHeight(float height)
    {
        //do not descrease the viewport height under the initial value
        if (height > this.height)
            super.setContentHeight(height);
    }

    /**
     * Computes the offset of the background image positions given by the difference between the background
     * source element position and the viewport position.
     * @return the background offset
     */
    public Dimension getBackgroundOffset()
    {
        if (getRootBox() != null)
        {
            final Rectangle rbg = getRootBox().getAbsoluteBorderBounds();
            return new Dimension(rbg.x, rbg.y);
        }
        else
            return new Dimension();
    }

    /**
	 * Calculates the absolute positions and updates the viewport size
	 * in order to enclose all the boxes.
	 * @param min the minimal viewport dimensions
	 */
	public void updateBounds(Dimension min)
	{
		//first round - compute the viewport size
		maxx = min.width;
		maxy = min.height;
        absolutePositionsChildren();

		//update the size
		if (width < maxx) width = maxx;
		if (height < maxy) height = maxy;
		loadSizes();
	}

    @Override
    public boolean doLayout(float availw, boolean force, boolean linestart)
    {
        //remove previously splitted children from possible previous layout
        clearSplitted();

        //viewport has a siplified width computation algorithm
        float min = getMinimalContentWidth();
        float pref = Math.max(min, width);
        setContentWidth(pref);
        updateChildSizes();
        
        //the width should be fixed from this point
        widthComputed = true;
        
        /* Always try to use the full width. If the box is not in flow, its width
         * is updated after the layout */
        setAvailableWidth(totalWidth());
        
        if (!contblock)  //block elements containing inline elements only
            layoutInline();
        else //block elements containing block elements
            layoutBlocks();
        
        //allways fits as well possible
        return true;
    }
	
    @Override
    public boolean formsStackingContext()
    {
        return true;
    }
    
	@Override
    public void absolutePositions()
    {
	    if (parent == null) //viewport may be the root
	        absbounds = new Rectangle(bounds);
	    else //or a nested viewport
	        absbounds = new Rectangle(parent.getAbsoluteContentBounds());
	    
	    //clear this context if it exists (remove old children)
	    if (scontext != null)
            scontext.clear();
	    
	    absolutePositionsChildren();
    }
	
	/**
	 * Computes the absolute positions of the child boxes.
	 */
	protected void absolutePositionsChildren()
	{
        //first round: position most boxes
        recomputeAbs = false;
        for (int i = 0; i < getSubBoxNumber(); i++)
            getSubBox(i).absolutePositions();
        if (recomputeAbs)
        {
            //second round: some reference boxes used, recompute once again
            if (scontext != null) //clear the stacking context if it exists -- the child contexts will register again
                scontext.clear();
            for (int i = 0; i < getSubBoxNumber(); i++)
                getSubBox(i).absolutePositions();
            recomputeAbs = false;
        }
	}
	
    /**
     * Sets the current renderer and draws the whole subtree using the given renderer.
     * @param renderer The renderer to be used for drawing.
     */
    public void draw(BoxRenderer renderer)
    {
        this.renderer = renderer;
        drawStackingContext(false);
    }
	
    /**
     * Obtains the current renderer used for painting the boxes.
     * @return current renderer.
     */
    public BoxRenderer getRenderer()
    {
        return renderer;
    }
    
	/**
	 * Updates the maximal viewport size according to the element bounds
	 */
	public void updateBoundsFor(Rectangle bounds)
	{
		float x = bounds.x + bounds.width - 1;
		if (maxx < x) maxx = x;
		float y = bounds.y + bounds.height - 1;
		if (maxy < y) maxy = y;
	}
	
	/**
	 * Indicates that the absolute positions need to be recomputed onec again. This happens when
	 * some absolutely positioned box has a 'static' position depending on some in-flow box
	 * and the position of the in-flow box changed.
	 */
	public void requireRecomputePositions()
	{
	    recomputeAbs = true;
	}
	
	/**
	 * Uses the given block as a clipping block instead of the default Viewport.
	 * @param block the new clipping block
	 */
	public void clipByBlock(BlockBox block)
	{
	    recursivelySetClipBlock(this, block);
	}
	
	private void recursivelySetClipBlock(Box root, BlockBox clip)
	{
	    if (root == this || root.getClipBlock() == this)
	    {
	        root.setClipBlock(clip);
	        if (root instanceof ElementBox)
	        {
	            ElementBox eb = (ElementBox) root;
	            for (int i = eb.getStartChild(); i < eb.getEndChild(); i++)
	                recursivelySetClipBlock(eb.getSubBox(i), clip);
	        }
	    }
	}
	
	/**
	 * Tries to propagate the overflow value to viewport from the element based on
	 * https://drafts.csswg.org/css-overflow-3/#overflow-propagation
	 * If the conditions are met, the overflow values are propagated. Othervise, no action is taken.
	 * 
	 * @param block the box to be used as a possible source of the overflow values
	 * @return {@code true} when the propagation is finished (no more boxes may be used as the source), {@code false} when other boxes should be tried.
	 */
	public boolean checkPropagateOverflow(BlockBox block)
	{
	    if (rootBox == null && block.getElement() == root) //this block will become a new root block; use it
	    {
	        rootOverflowVisible = (block.getOverflowX() == BlockBox.OVERFLOW_VISIBLE);
	        takeBoxOverflow(block);
	        return !rootOverflowVisible || !config.getUseHTML(); //in HTML mode, the "body" element may be used as well thus we should continue when root element is not visible
	    }
	    else if ("body".equals(block.getElement().getTagName()))
	    {
	        if (config.getUseHTML())
	            takeBoxOverflow(block);
	        return true; //that should be all, we are finished
	    }
	    else
	        return false; //this one could not be used, go on
	}
	
	private void takeBoxOverflow(BlockBox srcBlock)
	{
        //use the propagated value for the viewport
        overflowX = srcBlock.overflowX;
        overflowY = srcBlock.overflowY;
        //use VISIBLE for the original block
        srcBlock.overflowX = BlockBox.OVERFLOW_VISIBLE;
        srcBlock.overflowY = BlockBox.OVERFLOW_VISIBLE;
        //apply CSSBox-specific viewport clipping config
        if (config.getClipViewport())
        {
            overflowX = BlockBox.OVERFLOW_HIDDEN;
            overflowY = BlockBox.OVERFLOW_HIDDEN;
        }
        else
        {
            //CSSBox treats the overflow:visible as scrollable for now, i.e. scroll and auto values should be converted to visible
            if (overflowX != BlockBox.OVERFLOW_HIDDEN)
                overflowX = BlockBox.OVERFLOW_VISIBLE;
            if (overflowY != BlockBox.OVERFLOW_HIDDEN)
                overflowY = BlockBox.OVERFLOW_VISIBLE;
        }
	}
	
    //===================================================================================
	
    @Override
    public float getContentX()
    {
        return visibleRect.x;
    }

    @Override
    public float getContentY()
    {
        return visibleRect.y;
    }

    @Override
    public float getContentWidth()
    {
        return visibleRect.width;
    }

    @Override
    public float getContentHeight()
    {
        return visibleRect.height;
    }

	@Override
    public Rectangle getAbsoluteBorderBounds()
    {
        return new Rectangle(visibleRect);
    }
	
    @Override
    public Rectangle getClippedBounds()
    {
        if (config.getClipViewport())
            return getAbsoluteBounds();
        else
            return new Rectangle(0, 0, width, height);
    }

    @Override
    public Rectangle getClippedContentBounds()
    {
        if (config.getClipViewport())
            return getAbsoluteBounds();
        else
            return new Rectangle(0, 0, width, height);
    }
	
    //===================================================================================
    
    private ElementBox recursiveFindElementBoxByName(ElementBox ebox, String name, boolean case_sensitive)
    {
        boolean eq;
        if (case_sensitive)
            eq = ebox.getElement().getTagName().equals(name);
        else
            eq = ebox.getElement().getTagName().equalsIgnoreCase(name);
        
        if (eq)
            return ebox;
        else
        {
            ElementBox ret = null;
            for (int i = 0; i < ebox.getSubBoxNumber() && ret == null; i++)
            {
                Box child = ebox.getSubBox(i);
                if (child instanceof ElementBox)
                    ret = recursiveFindElementBoxByName((ElementBox) child, name, case_sensitive);
            }
            return ret;
        }
    }

}
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import org.fit.cssbox.awt.BrowserCanvas;
import org.fit.cssbox.css.BackgroundDecoder;
import org.fit.cssbox.css.CSSNorm;
import org.fit.cssbox.css.CSSUnits;
import org.fit.cssbox.css.DOMAnalyzer;
import org.fit.cssbox.io.DOMSource;
import org.fit.cssbox.io.DefaultDOMSource;
import org.fit.cssbox.io.DefaultDocumentSource;
import org.fit.cssbox.io.DocumentSource;
import org.fit.cssbox.layout.BlockBox;
import org.fit.cssbox.layout.Box;
import org.fit.cssbox.layout.BrowserConfig;
import org.fit.cssbox.layout.ElementBox;
import org.fit.cssbox.layout.Inline;
import org.fit.cssbox.layout.InlineElement;
import org.fit.cssbox.layout.Rectangle;
import org.fit.cssbox.layout.TextBox;
import org.fit.cssbox.layout.Viewport;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import java.awt.GridBagConstraints;
import java.awt.GridLayout;
import javax.swing.table.DefaultTableModel;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import java.awt.GridBagLayout;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Rectangle2D;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics2D;
import cz.vutbr.web.css.Declaration;
import cz.vutbr.web.css.MediaSpec;
import cz.vutbr.web.css.NodeData;
import javax.swing.event.TreeSelectionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
/**
 * BoxBrowser.java 
 * Copyright (c) 2005-2014 Radek Burget
 *
 * CSSBox 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 3 of the License, or
 * (at your option) any later version.
 *  
 * CSSBox 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 CSSBox. If not, see <http://www.gnu.org/licenses/>.
 * 
 */
package org.fit.cssbox.demo;








/**
 * This demo implements a browser that displays the rendered box tree and the
 * corresponding page. Each box corresponds to an HTML element or a text node.
 * 
 * @author burgetr
 */
public class BoxBrowser
{
    protected DefaultMutableTreeNode root;
    protected DefaultMutableTreeNode domRoot;
    protected BrowserConfig config;