/**
* ===========================================
* LibLayout : a free Java layouting library
* ===========================================
*
* Project Info: http://reporting.pentaho.org/liblayout/
*
* (C) Copyright 2006-2007, by Pentaho Corporation and Contributors.
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 this
* library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* [Java is a trademark or registered trademark of Sun Microsystems, Inc.
* in the United States and other countries.]
*
* ------------
* $Id: ValidateModelStep.java 6653 2008-12-02 14:53:40Z tmorgner $
* ------------
* (C) Copyright 2006-2007, by Pentaho Corporation.
*/
package org.jfree.layouting.renderer.process;
import org.jfree.layouting.input.style.keys.box.DisplayRole;
import org.jfree.layouting.renderer.model.BlockRenderBox;
import org.jfree.layouting.renderer.model.InlineRenderBox;
import org.jfree.layouting.renderer.model.NormalFlowRenderBox;
import org.jfree.layouting.renderer.model.ParagraphRenderBox;
import org.jfree.layouting.renderer.model.RenderNode;
import org.jfree.layouting.renderer.model.page.LogicalPageBox;
import org.jfree.layouting.renderer.model.table.TableCellRenderBox;
import org.jfree.layouting.renderer.model.table.TableRenderBox;
import org.jfree.layouting.renderer.model.table.TableRowInfoStructure;
import org.jfree.layouting.renderer.model.table.TableRowRenderBox;
import org.jfree.layouting.renderer.model.table.TableSectionRenderBox;
import org.jfree.layouting.renderer.model.table.cells.TableCell;
/**
* This step checks, whether the model will be layoutable.
*
* Closed nodes are always layoutable. Nodes are non-layoutable, if they
* contain boxes with a width or margin of 'auto', tables with auto-width
* columns or if there is an open out-of-normal-flow element (floating or
* positioned element).
*
* if the preferred width is AUTO, then we have to check the whole
* thing. If (according to CSS3-Box) the width computes to the intrinsic
* width, then the layout is not computable yet.
*
* For now, we always assume that all elements have a horizontal flow.
* That simplifies all tests to whether the element is a flow root.
*
* For tables, the choice is a bit more complex. If the table uses the auto
* table-layout algorithm, the model is layoutable, if the table has no
* auto-width columns. If the table uses the fixed layout algorithm, the
* width of the table is auto but all columns have a non-auto-width, then
* the model will be layoutable at once. Columns with an auto-width in a
* fixed table model become layoutable as soon as the first row of data has
* been given.
*
* @author Thomas Morgner
*/
public class ValidateModelStep extends IterateStructuralProcessStep
{
public static final int LAYOUT_OK = 0;
public static final int NEED_MORE_DATA = 1;
public static final int BOX_MUST_BE_CLOSED = 2;
private Object layoutFailureNodeId;
private int layoutFailureResolution;
private LogicalPageBox root;
public ValidateModelStep()
{
}
public synchronized boolean isLayoutable (final LogicalPageBox root)
{
this.layoutFailureNodeId = null;
this.layoutFailureResolution = LAYOUT_OK;
this.root = root;
startProcessing(root);
this.root = null;
return layoutFailureNodeId == null;
}
public Object getLayoutFailureNodeId()
{
return layoutFailureNodeId;
}
public int getLayoutFailureResolution()
{
return layoutFailureResolution;
}
protected void finishBlockBox(final BlockRenderBox box)
{
if (layoutFailureNodeId != null)
{
return;
}
// If we're here, we can be sure that there is no open row anymore.
// Open rows would have triggered the unlayoutable-flag already ..
if (box.isOpen() == false)
{
return;
}
if (box instanceof TableSectionRenderBox)
{
// check, if the section contains row-spanning cell declarations.
// if it does, check whether there are enough rows to make these
// constraints valid. If there are rows missing, then the section
// is not layoutable..
RenderNode node = box.getFirstChild();
int expectedRows = 0;
while (node != null)
{
if (node instanceof TableRowRenderBox == false)
{
node = node.getNext();
continue;
}
expectedRows -= 1;
final TableRowRenderBox row = (TableRowRenderBox) node;
final TableRowInfoStructure rowInfoStructure = row.getRowInfoStructure();
if (rowInfoStructure.isValidationDone())
{
// ok, we can take the shortcut ..
final int cellCount = rowInfoStructure.getCellCount();
for (int i = 0; i < cellCount; i++)
{
final TableCell cellAt = rowInfoStructure.getCellAt(i);
expectedRows = Math.max (expectedRows, cellAt.getRowSpan() - 1);
}
}
else
{
// the slow-lane: Look at the already declared cells ..
RenderNode nodeCell = row.getFirstChild();
while (nodeCell != null)
{
if (nodeCell instanceof TableCellRenderBox)
{
final TableCellRenderBox cellBox = (TableCellRenderBox) nodeCell;
expectedRows = Math.max (expectedRows, cellBox.getRowSpan() - 1);
}
nodeCell = nodeCell.getNext();
}
}
node = node.getNext();
}
if (expectedRows >= 1)
{
layoutFailureNodeId = box.getInstanceId();
layoutFailureResolution = NEED_MORE_DATA;
}
}
}
protected boolean startBlockBox(final BlockRenderBox box)
{
if (layoutFailureNodeId != null)
{
// we already hit an error, so why seek more ..
return false;
}
if (box.isOpen() == false)
{
return false;
}
if (box instanceof TableRenderBox)
{
final TableRenderBox table = (TableRenderBox) box;
if (table.isAutoLayout())
{
// Auto-Layout means, we have to see the complete table.
// Yes, that is expensive ..
layoutFailureNodeId = box.getInstanceId();
layoutFailureResolution = BOX_MUST_BE_CLOSED;
return false;
}
// now dive deeper. Seek the first occurence of an table-body element..
boolean foundBodyGroup = false;
RenderNode node = table.getFirstChild();
while (node != null)
{
if (node instanceof TableSectionRenderBox)
{
final TableSectionRenderBox section = (TableSectionRenderBox) node;
if (DisplayRole.TABLE_ROW_GROUP.equals(section.getDisplayRole()))
{
foundBodyGroup = true;
boolean foundRow = false;
// We found a tbody element ..
// next - check whether the first row is closed ..
RenderNode maybeRow = section.getVisibleFirst();
while (maybeRow != null)
{
if (maybeRow instanceof TableRowRenderBox)
{
if (maybeRow.isOpen())
{
// not layoutable - bail out ..
layoutFailureNodeId = maybeRow.getInstanceId();
layoutFailureResolution = BOX_MUST_BE_CLOSED;
return false;
}
foundRow = true;
}
maybeRow = maybeRow.getNext();
}
if (foundRow == false)
{
// not layoutable - bail out ..
layoutFailureNodeId = box.getInstanceId();
layoutFailureResolution = NEED_MORE_DATA;
return false;
}
}
}
node = node.getNext();
}
if (foundBodyGroup == false)
{
// even if the table will never declare a tbody-group, it will become
// layoutable as soon as it is closed.
layoutFailureNodeId = box.getInstanceId();
layoutFailureResolution = NEED_MORE_DATA;
return false;
}
}
else if (box instanceof TableRowRenderBox)
{
final TableRowRenderBox row = (TableRowRenderBox) box;
final TableSectionRenderBox section = (TableSectionRenderBox) row.getParent();
if (DisplayRole.TABLE_ROW_GROUP.equals(section.getDisplayRole()))
{
// we need to have at least one complete row of data (not counting header
// and footer sections. The first row has no prev-element, so we simply
// test whether there is one ..
if (row.getVisiblePrev() == null)
{
layoutFailureNodeId = box.getInstanceId();
layoutFailureResolution = BOX_MUST_BE_CLOSED;
return false;
}
}
}
else if (box instanceof ParagraphRenderBox)
{
// An paragraph must be complete before we can attempt to layout it.
// Without that constraint, we cannot guarantee that inline-block-elements
// will behave correctly.
layoutFailureNodeId = box.getInstanceId();
layoutFailureResolution = BOX_MUST_BE_CLOSED;
return false;
}
return true;
}
protected boolean startInlineBox(final InlineRenderBox box)
{
if (layoutFailureNodeId != null)
{
// no need to check any further ..
return false;
}
return super.startInlineBox(box);
}
protected void startNormalFlow(final NormalFlowRenderBox box)
{
if (layoutFailureNodeId != null)
{
// no need to check any further ..
return;
}
// todo: More generalization needed?
if (root.getContentArea() != box)
{
// we only accept the root flow as open flow.
if (box.isOpen())
{
layoutFailureNodeId = box.getInstanceId();
layoutFailureResolution = BOX_MUST_BE_CLOSED;
}
}
}
}