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

cz.vutbr.fit.layout.cssbox.impl.BoxNode Maven / Gradle / Ivy

The newest version!
/**
 * BoxNode.java
 *
 * Created on 2.6.2006, 11:39:46 by burgetr
 */
package cz.vutbr.fit.layout.cssbox.impl;

import java.util.HashMap;
import java.util.Map;

import org.fit.cssbox.css.CSSUnits;
import org.fit.cssbox.layout.Box;
import org.fit.cssbox.layout.ElementBox;
import org.fit.cssbox.layout.ReplacedBox;
import org.fit.cssbox.layout.ReplacedContent;
import org.fit.cssbox.layout.ReplacedImage;
import org.fit.cssbox.layout.TextBox;
import org.fit.cssbox.layout.Viewport;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

import cz.vutbr.fit.layout.impl.DefaultBox;
import cz.vutbr.fit.layout.model.Border;
import cz.vutbr.fit.layout.model.Color;
import cz.vutbr.fit.layout.model.ContentObject;
import cz.vutbr.fit.layout.model.Page;
import cz.vutbr.fit.layout.model.Rectangular;
import cz.vutbr.fit.layout.model.TextStyle;
import cz.vutbr.fit.layout.model.Border.Side;
import cz.vutbr.fit.layout.model.Border.Style;
import cz.vutbr.web.css.CSSProperty;
import cz.vutbr.web.css.NodeData;
import cz.vutbr.web.css.TermColor;
import cz.vutbr.web.css.CSSProperty.BorderStyle;

/**
 * A node of a tree of visual blocks.
 * 
 * @author burgetr
 */
public class BoxNode extends DefaultBox
{
    /** The CSSBox box that forms this node */
    protected Box box;
    
    /** The transformation that should be applied to the box */
    protected BoxTransform transform;
    
    /** Zoom relative to original box sizes */
    private float zoom;
    
    /** XPath ID of the source node */
    private String xpath;
    
    
    //===================================================================================
    
    /**
     * Creates a new node containing a box with a transparent background.
     * 
     * @param box the contained box
     * @param page containing page
     * @param zoom zoom factor to apply
     */
    public BoxNode(Box box, Page page, float zoom)
    {
        this(box, page, null, zoom);
    }
    
    /**
     * Creates a new node containing a box with a computed background. The background
     * is computed separately when creating the nodes because the Viewport (and some
     * table elements) are treated in a special way.
     * 
     * @param box the contained box
     * @param page containing page
     * @param bgColor computed backgound color to be used for the box
     * @param zoom zoom factor to apply
     */
    public BoxNode(Box box, Page page, Color bgColor, float zoom)
    {
        super();
        this.box = box;
        this.zoom = zoom;
        setBackgroundColor(bgColor);
        setPage(page);
        //copy the bounds from the box
        if (box != null)
        {
            loadBoxProperties();
            transform = new BoxTransform(box);
        }
    }

    @Override
    public String toString()
    {
        Box box = getBox();
        String ret = "";
        /*if (efficientBackground != null)
            ret += (box != null && isVisuallySeparated()) ? "+" : "-";*/
        ret += getOrder() + ": ";
        if (box == null)
            ret += "- empty -";
        else if (box instanceof Viewport)
            ret += box.toString();
        else if (box instanceof ElementBox)
        {
            ElementBox elem = (ElementBox) box;
            ret += elem.getElement().getTagName();
            ret += " [" + elem.getElement().getAttribute("id") + "]";
            ret += " [" + elem.getElement().getAttribute("class") + "]";
            ret += " B" + getBounds().toString();
            if (getVisualBounds() != null)
                ret += " V" + getVisualBounds().toString();
        }
        else if (box instanceof TextBox)
        {
            ret = ((TextBox) box).getText();
            ret += " (" + box.getAbsoluteBounds().x + ","
            			+ box.getAbsoluteBounds().y + ","
            			+ (box.getAbsoluteBounds().x + box.getAbsoluteBounds().width - 1) + ","
            			+ (box.getAbsoluteBounds().y + box.getAbsoluteBounds().height - 1) + ")";
        }
        else
            ret = "?: " + box.toString();
        
        return ret;
    }
    
    @Override
    public boolean equals(Object obj)
    {
        if (obj instanceof BoxNode)
        {
            return ((BoxNode) obj).getBox() == getBox();
        }
        else
            return false;
    }
    
    //===================================================================================

    /**
     * Loads the intrinsic box properties obtained from CSSBox.
     */
    private void loadBoxProperties()
    {
        setType(getIntrinsicType());
        setBounds(new Rectangular(getIntrinsicBounds()));
        setContentBounds(new Rectangular(getIntrinsicBounds()));
        setOwnText(recursiveGetText(this));
        for (Border.Side side : Border.Side.values())
        {
            setBorderStyle(side, getIntrinsicBorderStyle(side));
        }
        setColor(getIntrinsicColor());
        setFontFamily(getIntrinsicFontFamily());
        setContentObject(getIntrinsicContentObject());
        
        //initially, the box is considered to be background-separated if it has a declared background
        //later this is recomputed when the box tree is built and the efficient backgrounds are
        //computed
        setBackgroundSeparated((box instanceof ElementBox && ((ElementBox) box).getBgcolor() != null));
    }
    
    /**
     * Gets the original bounds of a box as provided by CSSBox.
     * @return the intrinsic bounds
     */
    public Rectangular getIntrinsicBounds()
    {
        final Box box = getBox();
        Rectangular ret = null;
        
        if (box instanceof Viewport)
        {
            ret = new RectangularZ(((Viewport) box).getClippedBounds(), zoom);
        }
        else if (box instanceof ElementBox)
        {
            final ElementBox elem = (ElementBox) box;
            ret = new RectangularZ(elem.getAbsoluteBorderBounds().intersection(elem.getClipBlock().getClippedContentBounds()), zoom);
        }
        else //not an element
        {
            ret = new RectangularZ(box.getAbsoluteBounds().intersection(box.getClipBlock().getClippedContentBounds()), zoom);
        }
        
        return ret;
    }
    
    public int getIntrinsicTopBorder()
    {
        Box box = getBox();
        if (box instanceof ElementBox)
            return zoom(((ElementBox) box).getBorder().top);
        else
            return 0;
    }

    public int getIntrinsicBottomBorder()
    {
        Box box = getBox();
        if (box instanceof ElementBox)
            return zoom(((ElementBox) box).getBorder().bottom);
        else
            return 0;
    }

    public int getIntrinsicLeftBorder()
    {
        Box box = getBox();
        if (box instanceof ElementBox)
            return zoom(((ElementBox) box).getBorder().left);
        else
            return 0;
    }

    public int getIntrinsicRightBorder()
    {
        Box box = getBox();
        if (box instanceof ElementBox)
            return zoom(((ElementBox) box).getBorder().right);
        else
            return 0;
    }

    public Border getIntrinsicBorderStyle(Side side)
    {
        Box box = getBox();
        if (box instanceof ElementBox)
        {
            final NodeData style = ((ElementBox) box).getStyle();
            TermColor tclr = style.getValue(TermColor.class, "border-"+side+"-color");
            CSSProperty.BorderStyle bst = style.getProperty("border-"+side+"-style");
            if (bst == null)
                bst = BorderStyle.NONE;
            
            Color clr = null;
            if (tclr != null)
                clr = Units.toColor(tclr.getValue());
            if (clr == null)
            {
                clr = Units.toColor(box.getVisualContext().getColor());
                if (clr == null)
                    clr = Color.BLACK;
            }

            int rwidth = 0;
            switch (side)
            {
                case BOTTOM:
                    rwidth = getIntrinsicBottomBorder();
                    break;
                case LEFT:
                    rwidth = getIntrinsicLeftBorder();
                    break;
                case RIGHT:
                    rwidth = getIntrinsicRightBorder();
                    break;
                case TOP:
                    rwidth = getIntrinsicTopBorder();
                    break;
            }
            
            Border.Style rstyle;
            switch (bst)
            {
                case NONE:
                case HIDDEN:
                    rstyle = Style.NONE;
                    break;
                case DASHED:
                    rstyle = Style.DASHED;
                    break;
                case DOTTED:
                    rstyle = Style.DOTTED;
                    break;
                case DOUBLE:
                    rstyle = Style.DOUBLE;
                    break;
                default:
                    rstyle = Style.SOLID;
                    break;
            }
            
            return new Border(rwidth, rstyle, clr);
        }
        else
            return new Border();
    }

    //===================================================================================
    
    /**
     * @return the contained box
     */
    public Box getBox()
    {
        return box;
    }
    
    private String recursiveGetText(BoxNode root)
    {
        Box box = root.getBox();
        if (box instanceof TextBox)
            return ((TextBox) box).getText();
        else
        {
            String ret = "";
            for (int i = 0; i < root.getChildCount(); i++)
            {
                if (ret.trim().length() > 0)
                    ret += " ";
                ret = ret + recursiveGetText((BoxNode) root.getChildAt(i)).trim();
            }
            return ret;
        }
    }

    //==================================================================================
    
    @Override
    protected void recomputeTextStyle()
    {
        TextStyle textStyle = getTextStyle();
        if (textStyle == null)
        {
            textStyle = new TextStyle();
            setTextStyle(textStyle);
        }
        else
            textStyle.reset();
        
        if (isLeaf())
        {
            int len = getText().trim().length();
            textStyle.setFontSizeSum(getIntrinsicFontSize() * len);
            textStyle.setFontWeightSum(getIntrinsicFontWeight() * len);
            textStyle.setFontStyleSum(getIntrinsicFontStyle() * len);
            textStyle.setUnderlineSum(getIntrinsicUnderline() * len);
            textStyle.setLineThroughSum(getIntrinsicLineThrough() * len);
            textStyle.setContentLength(len);
        }
        else
        {
            for (cz.vutbr.fit.layout.model.Box box : getChildren())
            {
                textStyle.updateAverages(box.getTextStyle());
            }
        }
    }
    
    public float getIntrinsicFontSize()
    {
        return CSSUnits.pixels(getBox().getVisualContext().getFontSize());
    }

    public float getIntrinsicFontStyle()
    {
        return getBox().getVisualContext().getFontInfo().isItalic() ? 1.0f : 0.0f;
    }

    public float getIntrinsicFontWeight()
    {
        return getBox().getVisualContext().getFontInfo().isBold() ? 1.0f : 0.0f;
    }

    public float getIntrinsicUnderline()
    {
        return getBox().getVisualContext().getTextDecoration().contains(CSSProperty.TextDecoration.UNDERLINE) ? 1.0f : 0.0f;
    }

    public float getIntrinsicLineThrough()
    {
        return getBox().getVisualContext().getTextDecoration().contains(CSSProperty.TextDecoration.LINE_THROUGH) ? 1.0f : 0.0f;
    }

    public Color getIntrinsicColor()
    {
        return Units.toColor(getBox().getVisualContext().getColor());
    }

    public String getIntrinsicFontFamily()
    {
        return getBox().getVisualContext().getFontInfo().getFamily();
    }

    public ContentObject getIntrinsicContentObject()
    {
        if (getBox().isReplaced())
        {
            ReplacedContent content = ((ReplacedBox) getBox()).getContentObj();
            if (content instanceof ReplacedImage)
                return new ContentImageImpl((ReplacedImage) content);
            else
                return null;
        }
        else
            return null;
    }

    public Type getIntrinsicType()
    {
        if (getBox().isReplaced())
            return Type.REPLACED_CONTENT;
        else if (getBox() instanceof TextBox)
            return Type.TEXT_CONTENT;
        else
            return Type.ELEMENT;
    }

    public Node getDOMNode()
    {
        return getBox().getNode();
    }
    
    @Override
    public String getSourceNodeId()
    {
        if (xpath == null)
        {
            if (getBox() instanceof Viewport)
                xpath = "viewport";
            else
                xpath = getPathTo(getBox().getNode());
        }
        return xpath;
    }

    @Override
    public String getTagName()
    {
        final Node node = getDOMNode();
        if (node != null && node.getNodeType() == Node.ELEMENT_NODE)
            return ((Element) node).getTagName().toLowerCase();
        else
            return null;
    }

    @Override
    public String getAttribute(String name)
    {
        final Node node = getDOMNode();
        if (node != null)
        {
            if ("href".equals(name))
                return getAncestorAttribute(node, "a", name);
            else
                return getElementAttribute(node, name);
        }
        else
            return null;
    }

    @Override
    public Map getAttributes()
    {
        final Node node = getDOMNode();
        NamedNodeMap map = null;
        if (node.getNodeType() == Node.ELEMENT_NODE)
        {
            map = node.getAttributes();
        }
        else if (node.getNodeType() == Node.TEXT_NODE) //text nodes -- try parent //TODO how to propagate from ancestors correctly?
        {
            final Node pnode = node.getParentNode();
            if (pnode != null && pnode.getNodeType() == Node.ELEMENT_NODE)
            {
                map = pnode.getAttributes();
            }
        }
        
        Map ret = new HashMap<>((map == null) ? 1 : (map.getLength() + 1));
        if (map != null) //store the attributes found
        {
            for (int i = 0; i < map.getLength(); i++)
            {
                final Node attr = map.item(i);
                ret.put(attr.getNodeName(), attr.getNodeValue());
            }
        }
        //eventually add the href value (which may be inherited from top)
        if (!ret.containsKey("href"))
        {
            String href = getAncestorAttribute(node, "a", "href");
            if (href != null)
                ret.put("href", href);
        }
        return ret;
    }

    protected String getElementAttribute(Node node, String attrName)
    {
        if (node.getNodeType() == Node.ELEMENT_NODE)
        {
            final Element el = (Element) node;
            if (el.hasAttribute(attrName))
                return el.getAttribute(attrName);
            else
                return null;
        }
        else if (node.getNodeType() == Node.TEXT_NODE) //text nodes -- try parent //TODO how to propagate from ancestors correctly?
        {
            final Node pnode = node.getParentNode();
            if (pnode != null && pnode.getNodeType() == Node.ELEMENT_NODE)
            {
                final Element parent = (Element) pnode;
                if (parent.hasAttribute(attrName))
                    return parent.getAttribute(attrName);
                else
                    return null;
            }
            else
                return null;
        }
        else
            return null;
    }
    
    protected String getAncestorAttribute(Node node, String elementName, String attrName)
    {
        Node cur = node;
        //find the parent with the given name
        while (cur.getNodeType() != Node.ELEMENT_NODE || !elementName.equals(cur.getNodeName()))
        {
            cur = cur.getParentNode();
            if (cur == null)
                return null;
        }
        //read the attribute
        final Element el = (Element) cur;
        if (el.hasAttribute(attrName))
            return el.getAttribute(attrName);
        else
            return null;
    }
    
    @Override
    public DisplayType getDisplayType()
    {
        Box box = getBox();
        if (box instanceof ElementBox)
        {
            CSSProperty.Display display = ((ElementBox) box).getDisplay();
            if (display == null)
                return DisplayType.BLOCK; //e.g. the viewport has no display value
            switch (display)
            {
                case BLOCK:
                    return DisplayType.BLOCK;
                case INLINE:
                    return DisplayType.INLINE;
                case INLINE_BLOCK:
                    return DisplayType.INLINE_BLOCK;
                case INLINE_TABLE:
                    return DisplayType.INLINE_TABLE;
                case LIST_ITEM:
                    return DisplayType.LIST_ITEM;
                case NONE:
                    return DisplayType.NONE;
                case RUN_IN:
                    return DisplayType.RUN_IN;
                case TABLE:
                    return DisplayType.TABLE;
                case TABLE_CAPTION:
                    return DisplayType.TABLE_CAPTION;
                case TABLE_CELL:
                    return DisplayType.TABLE_CELL;
                case TABLE_COLUMN:
                    return DisplayType.TABLE_COLUMN;
                case TABLE_COLUMN_GROUP:
                    return DisplayType.TABLE_COLUMN_GROUP;
                case TABLE_FOOTER_GROUP:
                    return DisplayType.TABLE_FOOTER_GROUP;
                case TABLE_HEADER_GROUP:
                    return DisplayType.TABLE_HEADER_GROUP;
                case TABLE_ROW:
                    return DisplayType.TABLE_ROW;
                case TABLE_ROW_GROUP:
                    return DisplayType.TABLE_ROW_GROUP;
                default:
                    return DisplayType.BLOCK; //this should not happen
            }
        }
        else
            return null;
    }

    @Override
    public Rectangular getSubstringBounds(int startPos, int endPos)
    {
        Box box = getBox();
        if (box instanceof TextBox)
        {
            Rectangular ret = new Rectangular(getVisualBounds());
            int origin = ret.getX1();
            int startOfs = zoom(((TextBox) box).getCharOffsetX(startPos));
            int endOfs = zoom(((TextBox) box).getCharOffsetX(endPos));
            ret.setX1(origin + startOfs);
            ret.setX2(origin + endOfs);
            return ret;
        }
        else
            return null;
    }

    private int zoom(float src)
    {
        return Math.round(src * zoom);
    }
    
    private String getPathTo(Node element) 
    {
        if (element == null)
        {
            return "-";
        }
        else
        {
            if (element instanceof Element)
            {
                if ("html".equalsIgnoreCase(((Element) element).getTagName()))
                    return "/html[1]";
                else if ("body".equalsIgnoreCase(((Element) element).getTagName()))
                        return "//body[1]";
            }
    
            if (element.getParentNode() != null)
            {
                int ix = 0;
                var siblings = element.getParentNode().getChildNodes();
                for (int i = 0; i < siblings.getLength(); i++) 
                {
                    final Node sibling = siblings.item(i);
                    if (sibling == element)
                    {
                        String suffix;
                        if (element.getNodeType() == Node.ELEMENT_NODE)
                            suffix = "*[" + (ix+1) +"]";
                        else
                            suffix = "node()[" + (ix+1) +"]";
                            
                        Node parent = element.getParentNode();
                        if (parent == null)
                            return "/" + suffix;
                        else
                            return getPathTo(element.getParentNode()) + "/" + suffix;
                    }
                    // for text nodes count all nodes, for elements only count elements 
                    if (sibling.getNodeType() == Node.ELEMENT_NODE || element.getNodeType() == Node.TEXT_NODE)
                        ix++;
                }
            }
            return ""; // this shouldn't happen
        }
    }
    
    
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy