All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fit.cssbox.layout.ElementBox Maven / Gradle / Ivy

Go to download

CSSBox is an (X)HTML/CSS rendering engine written in pure Java. Its primary purpose is to provide a complete information about the rendered page suitable for further processing. However, it also allows displaying the rendered document.

There is a newer version: 5.0.2
Show newest version
/*
 * ElementBox.java
 * Copyright (c) 2005-2007 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 .
 *
 * Created on 5. �nor 2006, 21:32
 */

package org.fit.cssbox.layout;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.List;
import java.awt.*;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;

import cz.vutbr.web.css.*;
import cz.vutbr.web.css.CSSProperty.BackgroundAttachment;
import cz.vutbr.web.css.CSSProperty.BackgroundRepeat;
import cz.vutbr.web.css.CSSProperty.ZIndex;

import org.fit.cssbox.css.CSSUnits;
import org.fit.cssbox.misc.CSSStroke;
import org.fit.net.DataURLHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.*;

/**
 * An abstract class representing a box formed by a DOM element. There are two
 * possible subclases: an inline box and a block box. The element box can contain
 * an arbitrary number of sub-boxes. Since the box can be split to several parts,
 * only a continuous part of the list is considered for rendering.
 * @author  radek
 */
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.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 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 pseudoStyle;
    
    /** Background images or null when there are no background images */
    protected Vector bgimages;
    
    /** Set to true when the element box contains only text boxes */
    protected boolean textonly;
    
    /** The map of related pseudo-elements (if any) */
    protected Map pseudoElements;
    
    //============================== Computed style ======================
    
    /** The display property value */
    protected CSSProperty.Display display;
    
    /** 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;
    
    /** Background color or null when transparent */
    protected Color bgcolor;

    /** 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 int lineHeight;
    
    /** the z-index flag: true when z-index is different from auto */
    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 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, Graphics2D g, VisualContext ctx)
    {
        super(n, g, 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();
	        pseudoElements = new HashMap();
	        startChild = 0;
	        endChild = 0;
	        isblock = false;
	        textonly = true;
        }
        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;
        bgcolor = (src.bgcolor == null) ? null : new Color(src.bgcolor.getRed(), src.bgcolor.getGreen(), src.bgcolor.getBlue(), src.bgcolor.getAlpha());
        bgimages = (src.bgimages == null) ? null : new Vector(src.bgimages);
        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;
    }
    
    /**
     * 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 int getBorderWidth(CSSDecoder dec, String property)
    {
        if (style != null)
        {
            CSSProperty.BorderWidth prop = style.getProperty(property);
            if (prop == CSSProperty.BorderWidth.length)
                return dec.getLength(style.getValue(TermLengthOrPercent.class, property), false, CSSUnits.MEDIUM_BORDER, 0, 0);
            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 true 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 true 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 true when line wrapping is allowed
     */
    @Override
    public boolean allowsWrapping()
    {
        return (whitespace == ElementBox.WHITESPACE_NORMAL
                || whitespace == ElementBox.WHITESPACE_PRE_WRAP
                || whitespace== ElementBox.WHITESPACE_PRE_LINE);
    }
    
    /**
     * @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 getBackgroundImages()
    {
        return bgimages;
    }

    /**
     * @param bgcolor the background color
     */
    public void setBgcolor(Color bgcolor)
    {
        this.bgcolor = bgcolor;
    }

    /**
     * @return the number of subboxes in this box
     */
    public int getSubBoxNumber()
    {
        return nested.size();
    }

    /**
     * @return list of all the subboxes 
     */
    public List 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)
    {
        box.setParent(this);
        nested.add(box);
        endChild++;
        if (isDisplayed() && !box.isEmpty())
            isempty = false;
        if (!(box instanceof TextBox))
            textonly = false;
    }
    
    /**
     * 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.PseudoDeclaration 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.PseudoDeclaration 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.PseudoDeclaration pseudo)
    {
        return pseudoElements.containsKey(pseudo);
    }
    
    /**
     * @return the width of the content without any margins and borders
     */
    public int getContentWidth()
    {
    	return content.width;
    }
    
    /**
     * @return the height of the content without any margins and borders
     */
    public int getContentHeight()
    {
    	return content.height;
    }
    
    /**
     * Obtains the computed value of the declared line height of the element.
     * @return the line height in pixels
     */
    public int 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 true when z-index has a value different form auto.
     */
    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 true when the element creates a new stacking context.
     */
    public boolean formsStackingContext()
    {
        return position != POS_STATIC;
    }
    
    /**
     * @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 int 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 int getContentOffsetY()
    {
        return emargin.top + border.top + padding.top;
    }
    
    public int getContentX()
    {
        return bounds.x + emargin.left + border.left + padding.left;
    }
    
    public int getContentY()
    {
        return bounds.y + emargin.top + border.top + padding.top;
    }
    
    public int getAbsoluteContentX()
    {
        return absbounds.x + emargin.left + border.left + padding.left;
    }
    
    public int getAbsoluteContentY()
    {
        return absbounds.y + emargin.top + border.top + padding.top;
    }
    
    public int totalWidth()
    {
        return emargin.left + border.left + padding.left + content.width +
            padding.right + border.right + emargin.right;
    }
    
    //totalHeight() differs for inline and block boxes
    
    public int 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()
    {
    	int 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());
        }
    }
    
    /**
     * @return the bounds of the background - the content and padding
     */
    public Rectangle getAbsoluteBackgroundBounds()
    {
        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);
    }

    /**
     * @return the bounds of the border - the content, padding and border
     */
    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);
    }
    
    @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 true 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 true 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())
                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 list = getStackingContext().getElementsForZIndex(zindex);
        if (list != null)
        {
            for (ElementBox elem : list)
            {
                elem.drawStackingContext(!elem.hasZIndex());
            }
        }
    }
    
    /** 
     * Draw the background and border of this box (no subboxes).
     * This method is normally called automatically from {@link Box#draw(DrawStage)}.
     * @param g the graphics context used for drawing 
     */
    public void drawBackground(Graphics2D g)
    {
        Color color = g.getColor(); //original color
        Shape oldclip = g.getClip(); //original clip region
        if (clipblock != null)
            g.setClip(applyClip(oldclip, clipblock.getClippedContentBounds()));
        ctx.updateGraphics(g);
        
        //top left corner
        int x = absbounds.x;
        int y = absbounds.y;

        //border bounds
        int bx1 = x + margin.left;
        int by1 = y + emargin.top;
        int bw = border.left + padding.left + content.width + padding.right + border.right;
        int bh = border.top + padding.top + content.height + padding.bottom + border.bottom;
        int bx2 = bx1 + bw - 1;
        int by2 = by1 + bh - 1;
        
        //draw the background - it should be visible below the border too
        if (bgcolor != null)
        {
            g.setColor(bgcolor);
            g.fillRect(bx1, by1, bw, bh);
        }
        
        //draw the background images
        if (bgimages != null)
        {
            for (BackgroundImage img : bgimages)
            {
                //img.draw(g, 0, 0);
                BufferedImage bimg = img.getBufferedImage();
                if (bimg != null)
                    g.drawImage(bimg, bx1 + border.left, by1 + border.top, null);
            }
        }
        
        //draw the border
        drawBorders(g, bx1, by1, bx2, by2);
        
        g.setClip(oldclip); //restore the clipping
        g.setColor(color); //restore original color
    }
    
    protected void drawBorders(Graphics2D g, int bx1, int by1, int bx2, int by2)
    {
        if (border.top > 0 && bx2 > bx1)
            drawBorder(g, bx1, by1, bx2, by1, border.top, 0, 0, "top", false);
        if (border.right > 0 && by2 > by1)
            drawBorder(g, bx2, by1, bx2, by2, border.right, -border.right + 1, 0, "right", true); 
        if (border.bottom > 0 && bx2 > bx1)
            drawBorder(g, bx1, by2, bx2, by2, border.bottom, 0, -border.bottom + 1, "bottom", true); 
        if (border.left > 0 && by2 > by1)
            drawBorder(g, bx1, by1, bx1, by2, border.left, 0, 0, "left", false); 
    }
    
    private void drawBorder(Graphics2D g, int x1, int y1, int x2, int y2, int width, 
                            int right, int down, String side, boolean reverse)
    {
        CSSProperty.BorderColor bclr = style.getProperty("border-"+side+"-color");
        TermColor tclr = style.getValue(TermColor.class, "border-"+side+"-color");
        CSSProperty.BorderStyle bst = style.getProperty("border-"+side+"-style");
        if (bst != CSSProperty.BorderStyle.HIDDEN && bclr != CSSProperty.BorderColor.TRANSPARENT)
        {
            //System.out.println("Elem: " + this + "side: " + side + "color: " + tclr);
            Color clr = null;
            if (tclr != null)
                clr = tclr.getValue();
            if (clr == null)
            {
                clr = ctx.getColor();
                if (clr == null)
                    clr = Color.BLACK;
            }
            g.setColor(clr);
            g.setStroke(new CSSStroke(width, bst, reverse));
            g.draw(new Line2D.Double(x1 + right, y1 + down, x2 + right, y2 + down));
        }
    }

    @Override
    public void drawExtent(Graphics2D g)
    {
    	//draw the full box
        g.setColor(Color.RED);
        g.drawRect(absbounds.x, absbounds.y, bounds.width, bounds.height);
    	
    	//draw the content box
        g.setColor(Color.ORANGE);
        g.drawRect(getAbsoluteContentX(), getAbsoluteContentY(), getContentWidth(), getContentHeight());
        
        //draw the real content box
        /*g.setColor(Color.GREEN);
        Rectangle r = getMinimalBounds();
        g.drawRect(r.x, r.y, r.width, r.height);*/
    }
    
    //=======================================================================
    
    /**
     * @return  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;
                int oldw = block.getContentWidth();
                int 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 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 true if the margins are adjoining
     */
    abstract public boolean marginsAdjoin();
    
    /**
     * @return true 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, int 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()
    {
        ctx.updateForGraphics(style, g);
        
        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 = Math.round(DEFAULT_LINE_HEIGHT * ctx.getFontHeight());
        else if (lh == CSSProperty.LineHeight.length)
        {
            TermLength len = style.getValue(TermLength.class, "line-height");
            lineHeight = (int) ctx.pxLength(len);
        }
        else if (lh == CSSProperty.LineHeight.percentage)
        {
            TermPercent len = style.getValue(TermPercent.class, "line-height");
            lineHeight = (int) 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 = Math.round(r * ctx.getFontHeight());
        }

        //whitespace
        whitespace = style.getProperty("white-space");
        if (whitespace == null) whitespace = WHITESPACE_NORMAL;
        
        //background
        loadBackground();
        
        //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;
    }
    
    /**
     * Loads the background information from the style
     */
    protected void loadBackground()
    {
        CSSProperty.BackgroundColor bg = style.getProperty("background-color");
        if (bg == CSSProperty.BackgroundColor.color)
        {
            TermColor bgc = style.getValue(TermColor.class, "background-color");
            bgcolor = bgc.getValue();
        }
        else
            bgcolor = null;

        bgimages = loadBackgroundImages(style);
    }
    
    /**
     * Loads background images from style. Considers their positions, repetition, etc. Currently, only a single background
     * image is supported (CSS2).
     * @param style the style containing the image specifiations
     * @return a list of images
     */
    protected Vector loadBackgroundImages(NodeData style)
    {
        CSSProperty.BackgroundImage img = style.getProperty("background-image");
        if (img == CSSProperty.BackgroundImage.uri)
        {
            try {
                Vector bgimages = new Vector(1);
                TermURI urlstring = style.getValue(TermURI.class, "background-image");
                URL url = DataURLHandler.createURL(urlstring.getBase(), urlstring.getValue());
                CSSProperty.BackgroundPosition position = style.getProperty("background-position");
                TermList positionValues = style.getValue(TermList.class, "background-position");
                CSSProperty.BackgroundRepeat repeat = style.getProperty("background-repeat");
                if (repeat == null) repeat = BackgroundRepeat.REPEAT;
                CSSProperty.BackgroundAttachment attachment = style.getProperty("background-attachment");
                if (attachment == null) attachment = BackgroundAttachment.SCROLL;
                BackgroundImage bgimg = new BackgroundImage(this, url, position, positionValues, repeat, attachment);
                bgimages.add(bgimg);
                return bgimages;
            } catch (MalformedURLException e) {
                log.warn(e.getMessage());
                return null;
            }
        }
        else
            return null;
    }
    
    /**
     * Loads the top, left, bottom and right coordinates from the style
     */
    protected void loadPosition()
    {
        CSSDecoder dec = new CSSDecoder(ctx);

        int contw = cblock.getContentWidth();
        int conth = cblock.getContentHeight();
        
        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 (position != POS_STATIC) //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();
            }
        }
    }
    
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy