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

cz.vutbr.fit.layout.segm.op.FindLineOperator Maven / Gradle / Ivy

The newest version!
/**
 * FindLineOperator.java
 *
 * Created on 24. 10. 2013, 9:56:16 by burgetr
 */
package cz.vutbr.fit.layout.segm.op;

import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cz.vutbr.fit.layout.api.Parameter;
import cz.vutbr.fit.layout.impl.BaseOperator;
import cz.vutbr.fit.layout.impl.ParameterBoolean;
import cz.vutbr.fit.layout.impl.ParameterFloat;
import cz.vutbr.fit.layout.model.Area;
import cz.vutbr.fit.layout.model.AreaTopology;
import cz.vutbr.fit.layout.model.AreaTree;
import cz.vutbr.fit.layout.model.Rectangular;
import cz.vutbr.fit.layout.segm.AreaStyle;
import cz.vutbr.fit.layout.segm.TreeOp;

/**
 * Detects the basic lines in the area tree and joins the appropriate areas so that a line
 * is the smallest visual area. 
 * @author burgetr
 */
public class FindLineOperator extends BaseOperator
{
    private static Logger log = LoggerFactory.getLogger(FindLineOperator.class);
    
    /** Area attribute key for storing the next area on line */
    public static final String LINE_NEXT = "core.line.next";
    /** Area attribute key for storing the previous area on line */
    public static final String LINE_PREV = "core.line.prev";
    
    /** Remove the sub-areas? */
    protected boolean flattenLines;
    
    /** Should the lines have a consistent visual style? */
    protected boolean useConsistentStyle;
    
    /** The maximal distance of two areas allowed within a single line (in 'em' units) */
    protected float maxLineEmSpace;
    
    
    public FindLineOperator()
    {
        flattenLines = false;
        useConsistentStyle = false;
        maxLineEmSpace = 1.5f;
    }
    
    public FindLineOperator(boolean flattenLines, boolean useConsistentStyle, float maxLineEmSpace)
    {
        this.flattenLines = flattenLines;
        this.useConsistentStyle = useConsistentStyle;
        this.maxLineEmSpace = maxLineEmSpace;
    }
    
    @Override
    public String getId()
    {
        return "FitLayout.Segm.FindLines";
    }
    
    @Override
    public String getName()
    {
        return "Find lines";
    }

    @Override
    public String getDescription()
    {
        return "Detects the basic lines in the area tree and joins the appropriate areas so that"
                + " a line is the smallest visual area.";
    }

    @Override
    public String getCategory()
    {
        return "Lines";
    }

    @Override
    public List defineParams()
    {
        List ret = new ArrayList<>(3);
        ret.add(new ParameterBoolean("useConsistentStyle", "Require consistent visual style of all areas to join them."));
        ret.add(new ParameterFloat("maxLineEmSpace", "Maximal horizontal distance between two areas in font size (em) units.", 0.0f, 100.0f));
        ret.add(new ParameterBoolean("flattenLines", "Remove the sub-areas of the detected lines."));
        return ret;
    }

    public boolean getFlattenLines()
    {
        return flattenLines;
    }

    public void setFlattenLines(boolean flattenLines)
    {
        this.flattenLines = flattenLines;
    }

    public boolean getUseConsistentStyle()
    {
        return useConsistentStyle;
    }

    public void setUseConsistentStyle(boolean useConsistentStyle)
    {
        this.useConsistentStyle = useConsistentStyle;
    }

    public float getMaxLineEmSpace()
    {
        return maxLineEmSpace;
    }

    public void setMaxLineEmSpace(float maxLineEmSpace)
    {
        this.maxLineEmSpace = maxLineEmSpace;
    }

    //==============================================================================

    @Override
    public void apply(AreaTree atree)
    {
        recursiveJoinAreas(atree.getRoot());
    }

    @Override
    public void apply(AreaTree atree, Area root)
    {
        recursiveJoinAreas(root);
    }
    
    //==============================================================================
    
    
    /**
     * Goes through all the areas in the tree and tries to join their sub-areas into single
     * areas.
     * @param root The root area to process recursively.
     * @return {@code true} when entirely succeeded for the subtree, {@code false} when at least some
     * areas could not be joined.
     */
    protected boolean recursiveJoinAreas(Area root)
    {
        boolean success = true;
        for (int i = 0; i < root.getChildCount(); i++)
            success &= recursiveJoinAreas(root.getChildAt(i));
        if (success)
            success &= joinAreas(root);
        return success;
    }
    
    /**
     * Goes through the grid of areas and joins the adjacent visual areas that are not
     * separated by anything.
     * @param a The root area to process.
     * @return {@code true} when entirely succeeded, {@code false} when at least some
     * areas could not be joined.
     */
    protected boolean joinAreas(Area a)
    {
        //TODO: detekce radku by asi mela brat v uvahu separatory
        boolean ret = true;
        AreaTopology t = a.getTopology();
        
        boolean change = true;
        while (change)
        {
            change = false;
            for (int i = 0; i < a.getChildCount(); i++)
            {
                Area node = a.getChildAt(i);
                Rectangular pos = t.getPosition(node);
                int ny1 = pos.getY1();
                int nx2 = pos.getX2();
                int ny2 = pos.getY2();
                
                //try to expand to the right - find a neighbor
                Area neigh = null;
                int dist = 1;
                while (neigh == null && nx2 + dist < t.getTopologyWidth())
                {
                    //try to find some node at the right in the given distance
                    for (int y = ny1; neigh == null && y <= ny2; y++)
                    {
                        neigh = (Area) t.findAreaAt(nx2 + dist, y);
                        if (neigh != null) //something found
                        {
                            if (!useConsistentStyle || AreaStyle.hasSameStyle(node, neigh))
                            {
                                if (horizontalJoin(a, node, neigh, true)) //try to join
                                {
                                    node.updateTopologies();
                                    change = true;
                                }
                                else
                                    ret = false;
                            }
                            else
                            {
                                if (horizontalJoin(a, node, neigh, false)) //check if the nodes could be joined
                                {
                                    node.addUserAttribute(LINE_NEXT, neigh);
                                    neigh.addUserAttribute(LINE_PREV, node);
                                }
                                else
                                    ret = false;
                            }
                        }
                    }
                    dist++;
                }
                if (change) break; //something changed, repeat
            }
        }
        return ret;
    }

    /**
     * Joins two boxes horizontally into one area if the node heights are equal or they 
     * can be aligned to a rectangle using free spaces.
     * @param n1 left node to be aligned
     * @param n2 right node to be aligned
     * @param affect when set to true, the two nodes are joined and n2 is removed from the tree.
     *        When set to false, no changes are performed (only checking)
     * @return true when succeeded
     */
    private boolean horizontalJoin(Area parent, Area n1, Area n2, boolean affect)
    {
        //System.out.println("HJoin: " + n1.toString() + " + " + n2.toString());
        //check the maximal distance between the nodes
        int dist = Math.min(Math.abs(n2.getX1() - n1.getX2()), Math.abs(n1.getX1() - n2.getX2()));
        if (dist > n1.getTextStyle().getFontSize() * maxLineEmSpace)
            return false;
        //check if there is no separating border or background
        if (n1.hasRightBorder() || 
            n2.hasLeftBorder() ||
            !AreaStyle.hasEqualBackground(n1, n2))
            return false; //separated, give up
        //align the start
        int sy1 = n1.getGridPosition().getY1();
        int sy2 = n2.getGridPosition().getY1();
        while (sy1 != sy2)
        {
            if (sy1 < sy2) //n1 starts earlier, try to expand n2 up
            {
                if (sy2 > 0 && canExpandY(parent, n2, sy2-1, n1))
                    sy2--;
                else
                    return false; //cannot align - give up
            }
            else if (sy1 > sy2) //n2 starts earlier, try to expand n1 up
            {
                if (sy1 > 0 && canExpandY(parent, n1, sy1-1, n2))
                    sy1--;
                else
                    return false; //cannot align - give up
            }
        }
        //System.out.println("sy1="+sy1);
        //align the end
        int ey1 = n1.getGridPosition().getY2(); //last
        int ey2 = n2.getGridPosition().getY2();
        while (ey1 != ey2)
        {
            if (ey1 < ey2) //n1 ends earlier, try to expand n1 down
            {
                if (ey1 < parent.getTopology().getTopologyWidth()-1 && canExpandY(parent, n1, ey1+1, n2))
                    ey1++;
                else
                    return false; //cannot align - give up
            }
            else if (ey1 > ey2) //n2 ends earlier, try to expand n2 down
            {
                if (ey2 < parent.getTopology().getTopologyWidth()-1 && canExpandY(parent, n2, ey2+1, n1))
                    ey2++;
                else
                    return false; //cannot align - give up
            }
        }
        //System.out.println("ey1="+ey1);
        //align succeeded, join the areas
        if (affect)
        {
            log.debug("Join: {} + {}", n1, n2);
            Rectangular newpos = new Rectangular(n1.getGridPosition().getX1(), sy1,
                                                 n2.getGridPosition().getX2(), ey1);
            TreeOp.joinArea(n1, n2, newpos, true, flattenLines);
            parent.removeChild(n2);
        }
        return true;
    }
    
    
    /**
     * Checks if the area can be vertically expanded to the given 
     * Y coordinate, i.e. there is a free space in the space on this Y coordinate
     * for the whole width of the area.
     * @param node the area node that should be expanded
     * @param y the Y coordinate to that the area should be expanded
     * @param except an area that shouldn't be considered for conflicts (e.g. an overlaping area)
     * @return true if the area can be expanded
     */
    private boolean canExpandY(Area parent, Area node, int y, Area except)
    {
        AreaTopology t = parent.getTopology();
        int gx = t.getPosition(node).getX1();
        int gw = t.getTopologyWidth();
        for (int x = gx; x < gx + gw; x++)
        {
            Area cand = (Area) t.findAreaAt(x, y);
            if (cand != null && cand != except)
                return false; //something found - cannot expand
        }
        return true;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy