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

org.fit.cssbox.layout.InlineBox 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
/*
 * InlineBox.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, 13:38
 */

package org.fit.cssbox.layout;

import java.awt.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.*;
import cz.vutbr.web.css.*;

/**
 * An inline element box.
 *
 * @author  radek
 */
public class InlineBox extends ElementBox implements InlineElement
{
    private static Logger log = LoggerFactory.getLogger(InlineBox.class);
    
    /** vertical box alignment specified by the style */
    private CSSProperty.VerticalAlign valign;
    
    /** parent LineBox assigned during layout */
    private LineBox linebox;
    
    /** line box describing the children layout */
    private LineBox curline;
    
    /** half-lead after layout */
    private int halflead;
    
    /** Layout finished with a line break? */
    protected boolean lineBreakStop;
    
    //========================================================================
    
    /** Creates a new instance of InlineBox */
    public InlineBox(Element n, Graphics2D g, VisualContext ctx) 
    {
        super(n, g, ctx);
        halflead = 0;
        lineBreakStop = false;
    }
    
    public void copyValues(InlineBox src)
    {
        super.copyValues(src);
        valign = src.valign;
    }
    
    @Override
    public InlineBox copyBox()
    {
        InlineBox ret = new InlineBox(el, g, ctx);
        ret.copyValues(this);
        return ret;
    }
    
    //========================================================================
    
    @Override
    public String toString()
    {
        return "<" + el.getTagName() + " id=\"" + el.getAttribute("id") + 
               "\" class=\""  + el.getAttribute("class") + "\">";
    }
    
    @Override
    public void setStyle(NodeData s)
    {
        super.setStyle(s);
        loadInlineStyle();
    }
    
    public CSSProperty.VerticalAlign getVerticalAlign()
    {
        return valign;
    }
    
    /**
     * 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)
    {
        this.linebox = linebox;
        for (int i = startChild; i < endChild; i++)
        {
            Box sub = getSubBox(i);
            if (sub instanceof InlineElement)
                ((InlineElement) sub).setLineBox(linebox);
        }
    }
    
    /**
     * Returns the line box used for positioning this element.
     */
    public LineBox getLineBox()
    {
        return linebox;
    }
    
    //========================================================================
    
    public int getBaselineOffset()
    {
    	if (curline == null)
    		return 0;
    	else
    		return curline.getBaselineOffset();
    }
    
    public int getBelowBaseline()
    {
    	if (curline == null)
    		return 0;
    	else
    		return curline.getBelowBaseline();
    }
    
    public int getTotalLineHeight()
    {
    	if (curline == null)
    		return 0;
    	else
    		return curline.getTotalLineHeight();
    }
    
    public int getMaxLineHeight()
    {
        if (curline == null)
            return lineHeight;
        else
            return Math.max(lineHeight, curline.getMaxLineHeight());
    }
    
    public int getLineboxOffset()
    {
        if (curline == null)
            return 0;
        else
            return curline.getBaselineOffset() - ctx.getBaselineOffset() - halflead;
    }
    /**
     * Returns the half-lead value used for positioning the nested boxes within this inline box
     * @return half-lead value in pixels
     */
    public int getHalfLead()
    {
        return halflead;
    }
    
    public int getFirstLineLength()
    {
        if (preservesLineBreaks())
        {
            if (endChild > startChild)
                return ((Inline) getSubBox(startChild)).getFirstLineLength();
            else
                return 0;
        }
        else
        {
            int ret = 0;
            for (int i = startChild; i < endChild; i++)
                ret += getSubBox(i).getMaximalWidth();
            return ret;
        }
    }

    public int getLastLineLength()
    {
        if (preservesLineBreaks())
        {
            if (endChild > startChild)
                return ((Inline) getSubBox(endChild - 1)).getLastLineLength();
            else
                return 0;
        }
        else
        {
            int ret = 0;
            for (int i = startChild; i < endChild; i++)
                ret += getSubBox(i).getMaximalWidth();
            return ret;
        }
    }
    
    public boolean containsLineBreak()
    {
        for (int i = startChild; i < endChild; i++)
            if (((Inline) getSubBox(i)).containsLineBreak())
                return true;
        return false;
        
    }

    public boolean finishedByLineBreak()
    {
        return lineBreakStop;
    }
    
    //========================================================================
    
    @Override
    public boolean isInFlow()
	{
		return true;
	}
	
	@Override
    public boolean containsFlow()
	{
		return !isempty;
	}
	
    @Override
    public boolean mayContainBlocks()
    {
    	return false;
    }
    
    /** 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
     */
    @Override
    public boolean doLayout(int availw, boolean force, boolean linestart)
    {
        //if (getElement() != null && getElement().getAttribute("id").equals("mojo"))
        //  System.out.println("jo!");
        //Skip if not displayed
        if (!displayed)
        {
            content.setSize(0, 0);
            bounds.setSize(0, 0);
            return true;
        }

        setAvailableWidth(availw);
        
        curline = new LineBox(this, startChild, 0);
        int wlimit = getAvailableContentWidth();
        int x = 0; //current x
        boolean ret = true;
        rest = null;

        int lastbreak = startChild; //last possible position of a line break
        boolean lastwhite = false; //last box ends with a whitespace
        
        for (int i = startChild; i < endChild; i++)
        {
            Box subbox = getSubBox(i);
            if (subbox.canSplitBefore())
            	lastbreak = i;
            //when forcing, force the first child only and the children before
            //the first possible break
            boolean f = force && (i == startChild || lastbreak == startChild);
            if (lastwhite) subbox.setIgnoreInitialWhitespace(true);
            boolean fit = subbox.doLayout(wlimit - x, f, linestart && (i == startChild));
            if (fit) //something has been placed
            {
                if (subbox instanceof Inline)
                {
                    subbox.setPosition(x,  0); //the y position will be updated later
                    x += subbox.getWidth();
                    curline.considerBox((Inline) subbox);
                    if (((Inline) subbox).finishedByLineBreak())
                        lineBreakStop = true;
                }
                else
                	log.debug("Warning: doLayout(): subbox is not inline: " + subbox);
                if (subbox.getRest() != null) //is there anything remaining?
                {
                    InlineBox rbox = copyBox();
                    rbox.splitted = true;
                    rbox.splitid = splitid + 1;
                    rbox.setStartChild(i); //next starts with me...
                    rbox.nested.setElementAt(subbox.getRest(), i); //..but only with the rest
                    rbox.adoptChildren();
                    setEndChild(i+1); //...and this box stops with this element
                    rest = rbox;
                    break;
                }
                else if (lineBreakStop) //nothing remained but there was a line break
                {
                    if (i + 1 < endChild) //some children remaining
                    {
                        InlineBox rbox = copyBox();
                        rbox.splitted = true;
                        rbox.splitid = splitid + 1;
                        rbox.setStartChild(i + 1); //next starts with the next one
                        rbox.adoptChildren();
                        setEndChild(i+1); //...and this box stops with this element
                        rest = rbox;
                    }
                    break;
                }
            }
            else //nothing from the child has been placed
            {
                if (lastbreak == startChild) //no children have been placed, give up
                {
                    ret = false; 
                    break; 
                }
                else //some children have been placed, contintue the next time
                {
                    InlineBox rbox = copyBox();
                    rbox.splitted = true;
                    rbox.splitid = splitid + 1;
                    rbox.setStartChild(lastbreak); //next time start from the last break
                    rbox.adoptChildren();
                    setEndChild(lastbreak); //this box stops here
                    rest = rbox;
                    break;
                }
            }
            
            if (!subbox.isEmpty())
                lastwhite = subbox.collapsesSpaces() && subbox.endsWithWhitespace(); 
            if (subbox.canSplitAfter())
            	lastbreak = i+1;
        }
        
        //compute the vertical positions of the boxes
        //updateLineMetrics();
        content.width = x;
        content.height = (int) Math.round(ctx.getFontHeight() * 1.1); //based on browser behaviour observations
        halflead = (content.height - ctx.getFontHeight()) / 2;
        alignBoxes();
        setSize(totalWidth(), totalHeight());
        
        return ret;
    }
    
    @Override
    public void absolutePositions()
    {
        updateStackingContexts();
        if (isDisplayed())
        {
            //x coordinate is taken from the content edge
            absbounds.x = getParent().getAbsoluteContentX() + bounds.x;
            //y coordinate -- depends on the vertical alignment
            if (valign == CSSProperty.VerticalAlign.TOP)
            {
                //absbounds.y = linebox.getAbsoluteY() + (linebox.getLead() / 2) - getContentOffsetY();
                absbounds.y = linebox.getAbsoluteY() - getContentOffsetY();
            }
            else if (valign == CSSProperty.VerticalAlign.BOTTOM)
            {
                absbounds.y = linebox.getAbsoluteY() + linebox.getMaxLineHeight() - getContentHeight() - getContentOffsetY();
            }
            else //other positions -- set during the layout. Relative to the parent content edge.
            {
                absbounds.y = getParent().getAbsoluteContentY() + bounds.y;
            }

            //consider the relative position
            if (position == POS_RELATIVE)
            {
                absbounds.x += leftset ? coords.left : (-coords.right);
                absbounds.y += topset ? coords.top : (-coords.bottom);
            }
            
            //update the width and height according to overflow of the parent
            absbounds.width = bounds.width;
            absbounds.height = bounds.height;
            
            //repeat for all valid subboxes
            for (int i = startChild; i < endChild; i++)
                getSubBox(i).absolutePositions();
        }
    }

    @Override
    public int getMinimalWidth()
    {
        int ret = 0;
        if (allowsWrapping())
        {
            //return the maximum of the nested minimal widths that are separated
            for (int i = startChild; i < endChild; i++)
            {
                int w = getSubBox(i).getMinimalWidth();
                if (w > ret) ret = w;
            }
        }
        else if (preservesLineBreaks())
        {
            //return the maximum of the nested minimal widths and try to sum the siblings sharing the same line
            int total = 0;
            for (int i = startChild; i < endChild; i++)
            {
                Box cur = getSubBox(i);
                int w = cur.getMinimalWidth();
                if (w > ret) ret = w;
                
                total += ((Inline) cur).getFirstLineLength();
                if (total > ret) ret = total;
                if (((Inline) cur).containsLineBreak())
                    total = 0;
                total += ((Inline) cur).getLastLineLength();
            }
        }
        else
        {
            //no wrapping allowed, no preserved line breaks, return the sum
            for (int i = startChild; i < endChild; i++)
                ret += getSubBox(i).getMaximalWidth();
        }
        //increase by margin, padding, border
        ret += margin.left + padding.left + border.left +
               margin.right + padding.right + border.right;
        return ret;
    }
    
    @Override
    public int getMaximalWidth()
    {
        int ret = 0;
        if (!preservesLineBreaks())
        {
            //return the sum of all the elements inside
            for (int i = startChild; i < endChild; i++)
                ret += getSubBox(i).getMaximalWidth();
        }
        else
        {
            //return the maximum of the nested minimal widths and try to sum the siblings sharing the same line
            int total = 0;
            for (int i = startChild; i < endChild; i++)
            {
                Box cur = getSubBox(i);
                int w = cur.getMaximalWidth();
                if (w > ret) ret = w;
                
                total += ((Inline) cur).getFirstLineLength();
                if (total > ret) ret = total;
                if (((Inline) cur).containsLineBreak())
                    total = 0;
                total += ((Inline) cur).getLastLineLength();
            }
        }
        //increase by margin, padding, border
        ret += margin.left + padding.left + border.left +
               margin.right + padding.right + border.right;
        return ret;
    }
    
    /**
     * Returns the height of the box or the highest subbox.
     */
    public int getMaximalHeight()
    {
        int ret = getHeight();
        for (int i = startChild; i < endChild; i++)
        {
            Box sub = getSubBox(i);
            int h = 0;
            if (sub instanceof InlineBox)
                h = ((InlineBox) sub).getMaximalHeight();
            else
                h = sub.getHeight();
            
            if (h > ret) ret = h;
        }
        return ret;
    }
    
    @Override
    public boolean canSplitInside()
    {
        for (int i = startChild; i < endChild; i++)
            if (getSubBox(i).canSplitInside())
                return true;
        return false;
    }
    
    @Override
    public boolean canSplitBefore()
    {
        return (endChild > startChild) && getSubBox(startChild).canSplitBefore();
    }
    
    @Override
    public boolean canSplitAfter()
    {
        return (endChild > startChild) && getSubBox(endChild-1).canSplitAfter();
    }
    
    @Override
    public boolean startsWithWhitespace()
    {
        return (endChild > startChild) && getSubBox(startChild).startsWithWhitespace();
    }
    
    @Override
    public boolean endsWithWhitespace()
    {
        return (endChild > startChild) && getSubBox(endChild - 1).endsWithWhitespace();
    }
    
    @Override
    public void setIgnoreInitialWhitespace(boolean b)
    {
        if (endChild > startChild)
            getSubBox(startChild).setIgnoreInitialWhitespace(b);
    }
    
    @Override
    public void draw(DrawStage turn)
    {
        ctx.updateGraphics(g);
        if (displayed)
        {
            if (!this.formsStackingContext())
            {
                switch (turn)
                {
                    case DRAW_NONINLINE:
                    case DRAW_FLOAT:
                        //there should be no block-level or floating children here -- we do nothing
                        break;
                    case DRAW_INLINE:
                        getViewport().getRenderer().renderElementBackground(this);
                        getViewport().getRenderer().startElementContents(this);
                        drawChildren(turn);
                        getViewport().getRenderer().finishElementContents(this);
                        break;
                }
            }
        }
    }
    
    @Override
    public void drawExtent(Graphics2D g)
    {
        super.drawExtent(g);
        
        /*g.setColor(Color.MAGENTA);
        int y = getAbsoluteContentY() - getLineboxOffset();
        int h = 0;
        if (curline != null)
            h = curline.getTotalLineHeight();
        g.drawRect(getAbsoluteContentX(), y, getContentWidth(), h);
        
        g.setColor(Color.BLUE);
        y = y + getBaselineOffset();
        g.drawRect(getAbsoluteContentX(), y, getContentWidth(), 1);*/
    }

    @Override
    public int totalHeight()
    {
        //for inline boxes, the top and bottom margins don't apply
        return border.top + padding.top + content.height + padding.bottom + border.bottom;
    }
    
    //=======================================================================
    
    /**
     * Loads the basic style properties related to the inline elements.
     */
    protected void loadInlineStyle()
    {
        valign = style.getProperty("vertical-align");
        if (valign == null) valign = CSSProperty.VerticalAlign.BASELINE;
    }
    
    @Override
    protected void loadSizes()
    {
        CSSDecoder dec = new CSSDecoder(ctx);
        
        if (cblock == null)
            log.debug(this + " has no cblock");
        
        //containing box sizes
        int contw = cblock.getContentWidth();
        
        //top and bottom margins take no effect for inline boxes
        // http://www.w3.org/TR/CSS21/box.html#propdef-margin-top
        margin = new LengthSet();
        margin.right = dec.getLength(getLengthValue("margin-right"), style.getProperty("margin-right") == CSSProperty.Margin.AUTO, 0, 0, contw);
        margin.left = dec.getLength(getLengthValue("margin-left"), style.getProperty("margin-left") == CSSProperty.Margin.AUTO, 0, 0, contw);
        emargin = new LengthSet(margin);

        loadBorders(dec, 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);
        
        content = new Dimension(0, 0);
        
        loadPosition();
    }
    
    @Override
    public void updateSizes()
    {
    	//no update needed - inline box size depends on the contents only
    }
   
    @Override
    public boolean hasFixedWidth()
    {
    	return false; //depends on the contents
    }
    
    @Override
    public boolean hasFixedHeight()
    {
    	return false; //depends on the contents
    }
    
    @Override
    public void computeEfficientMargins()
    {
        emargin.top = margin.top; //no collapsing is applied to inline boxes
        emargin.bottom = margin.bottom;
    }

    @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
    	{
    		//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;
    	}
	}
    
    //=====================================================================================================

    /**
     * Vertically aligns the contained boxes according to their vertical-align properties.
     */
    private void alignBoxes()
    {
        for (int i = startChild; i < endChild; i++)
        {
            Box sub = getSubBox(i);
            if (!sub.isBlock())
            {
                //position relative to the line box
                int dif = curline.alignBox((Inline) sub);
                //recompute to the content box
                dif = dif - getLineboxOffset();
                //recompute to the bounding box
                if (sub instanceof InlineBox)
                    dif = dif - ((ElementBox) sub).getContentOffsetY();
                
                if (dif != 0)
                    sub.moveDown(dif);
            }
        }
    }
    
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy