/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program 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.
*
* Copyright (c) 2001 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.layout.process;
import org.pentaho.reporting.engine.classic.core.ElementAlignment;
import org.pentaho.reporting.engine.classic.core.layout.model.FinishedRenderNode;
import org.pentaho.reporting.engine.classic.core.layout.model.LayoutNodeTypes;
import org.pentaho.reporting.engine.classic.core.layout.model.LogicalPageBox;
import org.pentaho.reporting.engine.classic.core.layout.model.ParagraphPoolBox;
import org.pentaho.reporting.engine.classic.core.layout.model.ParagraphRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderLength;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderNode;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderableReplacedContentBox;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderableText;
import org.pentaho.reporting.engine.classic.core.layout.model.WatermarkAreaBox;
import org.pentaho.reporting.engine.classic.core.layout.model.context.BoxDefinition;
import org.pentaho.reporting.engine.classic.core.layout.model.context.StaticBoxLayoutProperties;
import org.pentaho.reporting.engine.classic.core.layout.model.table.TableCellRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.table.TableRowRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.table.TableSectionRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorFeature;
import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorMetaData;
import org.pentaho.reporting.engine.classic.core.layout.process.util.CacheBoxShifter;
import org.pentaho.reporting.engine.classic.core.layout.process.util.MajorAxisParagraphBreakState;
import org.pentaho.reporting.engine.classic.core.layout.process.util.ProcessUtility;
import org.pentaho.reporting.engine.classic.core.layout.process.util.ReplacedContentUtil;
import org.pentaho.reporting.engine.classic.core.layout.process.valign.BoxAlignContext;
import org.pentaho.reporting.engine.classic.core.layout.process.valign.InlineBlockAlignContext;
import org.pentaho.reporting.engine.classic.core.layout.process.valign.NodeAlignContext;
import org.pentaho.reporting.engine.classic.core.layout.process.valign.ReplacedContentAlignContext;
import org.pentaho.reporting.engine.classic.core.layout.process.valign.TextElementAlignContext;
import org.pentaho.reporting.engine.classic.core.layout.process.valign.VerticalAlignmentProcessor;
/**
* Computes the absolute layout. The computed height and y positions of all abolutely positioned elements will be stored
* in the 'canvasY' and 'canvasHeight' properties of RenderNode. Percentages will be resolved to zero.
*
* @author Thomas Morgner
* @noinspection PointlessArithmeticExpression, ConstantConditions
*/
public final class InfiniteMajorAxisLayoutStep extends AbstractMajorAxisLayoutStep {
private MajorAxisParagraphBreakState breakState;
private VerticalAlignmentProcessor processor;
private boolean complexText;
public InfiniteMajorAxisLayoutStep() {
super( false );
this.breakState = new MajorAxisParagraphBreakState();
this.processor = new VerticalAlignmentProcessor();
}
public void initialize( OutputProcessorMetaData metaData ) {
complexText = metaData.isFeatureSupported( OutputProcessorFeature.COMPLEX_TEXT );
}
public void compute( final LogicalPageBox pageBox ) {
this.breakState.deinit();
try {
super.compute( pageBox );
} finally {
this.breakState.deinit();
}
}
/**
* Continues processing. The renderbox must have a valid x-layout (that is: X, content-X1, content-X2 and Width)
*
* @param box
* the box.
*/
public void continueComputation( final RenderBox box ) {
// This is most-likely wrong, but as we do not support inline-block elements yet, we can ignore this for now.
if ( box.getCachedWidth() == 0 ) {
throw new IllegalStateException( "Box must be layouted a bit .." );
}
this.breakState.deinit();
try {
super.continueComputation( box );
} finally {
this.breakState.deinit();
}
}
protected boolean startBlockLevelBox( final RenderBox box ) {
if ( checkCacheValid( box ) ) {
return false;
}
performStartTable( box );
// Compute the block-position of the box. The box is positioned relative to the previous sibling or
// relative to the parent.
box.setCachedY( computeVerticalBlockPosition( box ) );
if ( breakState.isActive() ) {
if ( complexText ) {
return true;
}
// No breakstate and not being suspended? Why this?
if ( breakState.isSuspended() == false ) {
throw new IllegalStateException( "This cannot be." );
}
// this way or another - we are suspended now. So there is no need to look
// at the children anymore ..
// This code is only executed for inline-block elements. Inline-block elements are not part of
// the 0.8.9 or 1.0 engine layouting.
return false;
}
final int layoutNodeType = box.getLayoutNodeType();
if ( layoutNodeType == LayoutNodeTypes.TYPE_BOX_PARAGRAPH ) {
final ParagraphRenderBox paragraphBox = (ParagraphRenderBox) box;
// We cant cache that ... the shift operations later would misbehave
// One way around would be to at least store the layouted offsets
// (which should be immutable as long as the line did not change its
// contents) and to reapply them on each run. This is cheaper than
// having to compute the whole v-align for the whole line.
breakState.init( paragraphBox );
} else if ( layoutNodeType == LayoutNodeTypes.TYPE_BOX_CONTENT ) {
final RenderableReplacedContentBox contentBox = (RenderableReplacedContentBox) box;
contentBox.setCachedHeight( ReplacedContentUtil.computeHeight( contentBox, 0, contentBox.getCachedWidth() ) );
} else if ( layoutNodeType == LayoutNodeTypes.TYPE_BOX_WATERMARK ) {
final WatermarkAreaBox watermarkAreaBox = (WatermarkAreaBox) box;
box.setCachedHeight( watermarkAreaBox.getLogicalPage().getPageHeight() );
}
return true;
}
protected void processBlockLevelNode( final RenderNode node ) {
// This could be anything, text, or an image.
node.setCachedY( computeVerticalBlockPosition( node ) );
if ( node.getNodeType() == LayoutNodeTypes.TYPE_NODE_FINISHEDNODE ) {
final FinishedRenderNode fnode = (FinishedRenderNode) node;
node.setCachedHeight( fnode.getLayoutedHeight() );
}
}
protected void finishBlockLevelBox( final RenderBox box ) {
if ( checkCacheValid( box ) ) {
return;
}
performFinishTable( box );
final int nodeType = box.getNodeType();
if ( nodeType == LayoutNodeTypes.TYPE_BOX_WATERMARK ) {
final WatermarkAreaBox watermarkAreaBox = (WatermarkAreaBox) box;
box.setCachedHeight( watermarkAreaBox.getLogicalPage().getPageHeight() );
} else {
final int layoutNodeType = box.getLayoutNodeType();
final RenderBox watermark = isWatermark( box );
if ( watermark != null ) {
final WatermarkAreaBox watermarkAreaBox = (WatermarkAreaBox) watermark;
box.setCachedHeight( watermarkAreaBox.getLogicalPage().getPageHeight() );
} else if ( ( layoutNodeType & LayoutNodeTypes.MASK_BOX_BLOCK ) == LayoutNodeTypes.MASK_BOX_BLOCK ) {
box.setCachedHeight( computeBlockHeightAndAlign( box ) );
} else if ( ( layoutNodeType & LayoutNodeTypes.MASK_BOX_ROW ) == LayoutNodeTypes.MASK_BOX_ROW ) {
box.setCachedHeight( computeRowHeight( box, 0 ) );
} else {
box.setCachedHeight( computeCanvasHeight( box ) );
}
}
if ( breakState.isActive() ) {
final Object suspender = breakState.getSuspendItem();
if ( box.getInstanceId() == suspender ) {
breakState.setSuspendItem( null );
return;
}
if ( suspender != null ) {
return;
}
if ( nodeType == LayoutNodeTypes.TYPE_BOX_PARAGRAPH ) {
breakState.deinit();
}
}
}
private RenderBox isWatermark( final RenderBox box ) {
final RenderBox parent = box.getParent();
if ( parent == null ) {
return null;
}
if ( parent.getNodeType() == LayoutNodeTypes.TYPE_BOX_WATERMARK ) {
return parent;
}
final RenderBox parent2 = parent.getParent();
if ( parent2 == null ) {
return null;
}
if ( parent2.getNodeType() == LayoutNodeTypes.TYPE_BOX_WATERMARK ) {
return parent2;
}
return null;
}
public static long computeVerticalBlockPosition( final RenderNode node ) {
// we have no margins yet ..
final long marginTop = 0;
// The y-position of a box depends on the parent.
final RenderBox parent = node.getParent();
// A table row is something special. Although it is a block box,
// it layouts its children from left to right
if ( parent != null ) {
final RenderNode prev = node.getPrev();
if ( prev != null ) {
if ( prev.isVisible() ) {
// we have a silbling. Position yourself directly below your silbling ..
return ( marginTop + prev.getCachedY() + prev.getCachedHeight() );
} else {
return ( marginTop + prev.getCachedY() );
}
} else {
final StaticBoxLayoutProperties blp = parent.getStaticBoxLayoutProperties();
final BoxDefinition bdef = parent.getBoxDefinition();
final long insetTop = ( blp.getBorderTop() + bdef.getPaddingTop() );
return ( marginTop + insetTop + parent.getCachedY() );
}
} else {
// there's no parent ..
return ( marginTop );
}
}
private long computeBlockHeightAndAlign( final RenderBox box ) {
return computeBlockHeightAndAlign( box, box.getBoxDefinition(), 0, true );
}
private static long computeTableHeightAndAlign( final RenderBox box ) {
return computeBlockHeightAndAlign( box, BoxDefinition.EMPTY, 0, true );
}
public static long computeBlockHeightAndAlign( final RenderBox box, final BoxDefinition boxDefinition,
final long resolveSize, final boolean alignChilds ) {
if ( resolveSize < 0 ) {
throw new IllegalArgumentException( "ResovleSize cannot be negative" );
}
// Check the height. Set the height.
final RenderLength preferredHeight = boxDefinition.getPreferredHeight();
final RenderLength minimumHeight = boxDefinition.getMinimumHeight();
final RenderLength maximumHeight = boxDefinition.getMaximumHeight();
final long usedHeight;
final long childY2;
final long childY1;
final RenderNode lastChildNode = box.getLastChild();
if ( lastChildNode != null ) {
childY1 = box.getFirstChild().getCachedY();
if ( lastChildNode.isVisible() ) {
childY2 =
lastChildNode.getCachedY() + lastChildNode.getCachedHeight() + lastChildNode.getEffectiveMarginBottom();
} else {
childY2 = lastChildNode.getCachedY();
}
usedHeight = ( childY2 - childY1 );
} else {
usedHeight = 0;
childY2 = 0;
childY1 = 0;
}
// final long blockContextWidth = box.getStaticBoxLayoutProperties().getBlockContextWidth();
final long rminH = minimumHeight.resolve( resolveSize, 0 );
final long rmaxH = maximumHeight.resolve( resolveSize, InfiniteMajorAxisLayoutStep.MAX_AUTO );
final StaticBoxLayoutProperties blp = box.getStaticBoxLayoutProperties();
final long insetBottom = blp.getBorderBottom() + boxDefinition.getPaddingBottom();
final long insetTop = blp.getBorderTop() + boxDefinition.getPaddingTop();
// computed height is always the height of the content-box, excluding any paddings and borders
final long computedContentHeight;
if ( boxDefinition.isSizeSpecifiesBorderBox() ) {
final long rprefH = preferredHeight.resolve( resolveSize, usedHeight + insetTop + insetBottom );
final long specifiedHeight = ProcessUtility.computeLength( rminH, rmaxH, rprefH );
computedContentHeight = specifiedHeight - insetTop - insetBottom;
} else {
final long rprefH = preferredHeight.resolve( resolveSize, usedHeight );
computedContentHeight = ProcessUtility.computeLength( rminH, rmaxH, rprefH );
}
if ( alignChilds && lastChildNode != null ) {
// grab the node's y2
if ( computedContentHeight > usedHeight ) {
// we have extra space to distribute. So lets shift some boxes.
final ElementAlignment valign = box.getNodeLayoutProperties().getVerticalAlignment();
if ( ElementAlignment.BOTTOM.equals( valign ) ) {
final long boxBottom = ( box.getCachedY() + computedContentHeight - insetBottom );
final long delta = boxBottom - childY2;
CacheBoxShifter.shiftBoxChilds( box, delta );
} else if ( ElementAlignment.MIDDLE.equals( valign ) ) {
final long extraHeight = computedContentHeight - usedHeight;
final long boxTop = box.getCachedY() + insetTop + ( extraHeight / 2 );
final long delta = boxTop - childY1;
CacheBoxShifter.shiftBoxChilds( box, delta );
}
}
}
final long retval = Math.max( 0, computedContentHeight + insetTop + insetBottom );
// For the water-mark area, this computation is different. The Watermark-area uses the known height of
// the parent (=the page size)
if ( box.getNodeType() == LayoutNodeTypes.TYPE_BOX_WATERMARK ) {
final WatermarkAreaBox watermarkAreaBox = (WatermarkAreaBox) box;
final LogicalPageBox lpb = watermarkAreaBox.getLogicalPage();
// set the page-height as watermark size.
return Math.max( retval, Math.max( 0, lpb.getPageHeight() - insetTop - insetBottom ) );
}
return retval;
}
/**
* We will do the alignment during the CanvasMajorAxisLayoutStep.
*
* @param box
* the box to be computed. Must be a box with row-layout
* @param resolveSize
* the current height that makes 100%
* @return the row's height.
*/
private long computeRowHeight( final RenderBox box, final long resolveSize ) {
// For the water-mark area, this computation is different. The Watermark-area uses the known height of
// the parent (=the page size)
if ( box.getNodeType() == LayoutNodeTypes.TYPE_BOX_WATERMARK ) {
final WatermarkAreaBox watermarkAreaBox = (WatermarkAreaBox) box;
final LogicalPageBox lpb = watermarkAreaBox.getLogicalPage();
// set the page-height as watermark size.
return lpb.getPageHeight();
}
// Check the height. Set the height.
final BoxDefinition boxDefinition = box.getBoxDefinition();
final RenderLength preferredHeight = boxDefinition.getPreferredHeight();
final RenderLength minimumHeight = boxDefinition.getMinimumHeight();
final RenderLength maximumHeight = boxDefinition.getMaximumHeight();
final StaticBoxLayoutProperties blp = box.getStaticBoxLayoutProperties();
final long insetBottom = blp.getBorderBottom() + boxDefinition.getPaddingBottom();
final long insetTop = blp.getBorderTop() + boxDefinition.getPaddingTop();
// usedHeight already contains the insetsTop ..
final long usedHeight;
RenderNode child = box.getFirstChild();
if ( child != null ) {
long maxChildY2 = 0;
while ( child != null ) {
if ( child.isVisible() ) {
final long childY2 = child.getCachedY() + child.getCachedHeight() + child.getEffectiveMarginBottom();
maxChildY2 = Math.max( childY2, maxChildY2 );
}
child = child.getNext();
}
usedHeight = ( maxChildY2 - box.getCachedY() );
} else {
usedHeight = insetTop;
}
final long rminH = minimumHeight.resolve( resolveSize, 0 );
final long rmaxH = maximumHeight.resolve( resolveSize, InfiniteMajorAxisLayoutStep.MAX_AUTO );
final long computedHeight; // always the height of the content box
if ( boxDefinition.isSizeSpecifiesBorderBox() ) {
final long rprefH = preferredHeight.resolve( resolveSize, usedHeight + insetBottom );
final long specifiedHeight = ProcessUtility.computeLength( rminH, rmaxH, rprefH );
computedHeight = Math.max( 0, specifiedHeight - insetTop - insetBottom );
} else {
final long rprefH = preferredHeight.resolve( resolveSize, usedHeight - insetTop );
computedHeight = Math.max( 0, ProcessUtility.computeLength( rminH, rmaxH, rprefH ) );
}
return Math.max( 0, computedHeight + insetTop + insetBottom );
}
protected void processParagraphChilds( final ParagraphRenderBox box ) {
if ( complexText ) {
processBoxChilds( box );
} else {
// Process the direct childs of the paragraph
// Each direct child represents a line ..
RenderNode node = box.getFirstChild();
while ( node != null ) {
// all childs of the linebox container must be inline boxes. They
// represent the lines in the paragraph. Any other element here is
// a error that must be reported
final ParagraphPoolBox inlineRenderBox = (ParagraphPoolBox) node;
if ( startLine( inlineRenderBox ) ) {
processBoxChilds( inlineRenderBox );
finishLine( inlineRenderBox );
}
node = node.getNext();
}
}
}
private boolean startLine( final ParagraphPoolBox box ) {
box.setCachedY( computeVerticalBlockPosition( box ) );
if ( breakState.isActive() == false ) {
return false;
}
if ( breakState.isSuspended() ) {
return false;
}
breakState.openContext( box );
return true;
}
private void finishLine( final ParagraphPoolBox inlineRenderBox ) {
if ( breakState.isActive() == false || breakState.isSuspended() ) {
return;
}
final BoxAlignContext boxAlignContext = breakState.closeContext();
// This aligns all direct childs. Once that is finished, we have to
// check, whether possibly existing inner-paragraphs are still valid
// or whether moving them violated any of the inner-pagebreak constraints.
final StaticBoxLayoutProperties blp = inlineRenderBox.getStaticBoxLayoutProperties();
final BoxDefinition bdef = inlineRenderBox.getBoxDefinition();
final long insetTop = ( blp.getBorderTop() + bdef.getPaddingTop() );
final long contentAreaY1 = inlineRenderBox.getCachedY() + insetTop;
final long lineHeight = inlineRenderBox.getLineHeight();
processor.align( boxAlignContext, contentAreaY1, lineHeight );
}
protected boolean startInlineLevelBox( final RenderBox box ) {
if ( checkCacheValid( box ) ) {
return false;
}
box.setCachedY( computeVerticalInlinePosition( box ) );
computeBaselineInfo( box );
if ( breakState == null ) {
// ignore .. should not happen anyway ..
return true;
}
if ( breakState.isSuspended() ) {
return false;
}
final int nodeType = box.getLayoutNodeType();
if ( ( nodeType & LayoutNodeTypes.MASK_BOX_INLINE ) == LayoutNodeTypes.MASK_BOX_INLINE ) {
breakState.openContext( box );
return true;
} else if ( nodeType == LayoutNodeTypes.TYPE_BOX_CONTENT ) {
breakState.getCurrentLine().addChild( new ReplacedContentAlignContext( (RenderableReplacedContentBox) box, 0 ) );
return false;
}
breakState.getCurrentLine().addChild( new InlineBlockAlignContext( box ) );
breakState.setSuspendItem( box.getInstanceId() );
return false;
}
private void computeBaselineInfo( final RenderBox box ) {
if ( box.getBaselineInfo() == null ) {
return;
}
RenderNode node = box.getFirstChild();
while ( node != null ) {
if ( node.getNodeType() == LayoutNodeTypes.TYPE_NODE_TEXT ) {
// grab the baseline info from there ...
final RenderableText text = (RenderableText) node;
box.setBaselineInfo( text.getBaselineInfo() );
break;
}
node = node.getNext();
}
// If we have no baseline info here, ask the parent. If that one has none
// either, then we cant do anything about it.
if ( box.getBaselineInfo() == null ) {
box.setBaselineInfo( box.getStaticBoxLayoutProperties().getNominalBaselineInfo() );
}
}
protected void processInlineLevelNode( final RenderNode node ) {
// compute the intial position.
node.setCachedY( computeVerticalInlinePosition( node ) );
// the height and the real position will be computed during the vertical-alignment computation.
if ( breakState.isActive() == false || breakState.isSuspended() ) {
return;
}
if ( complexText ) {
return;
}
if ( node.getNodeType() == LayoutNodeTypes.TYPE_NODE_TEXT ) {
breakState.getCurrentLine().addChild( new TextElementAlignContext( (RenderableText) node ) );
} else {
breakState.getCurrentLine().addChild( new NodeAlignContext( node ) );
}
}
protected void finishInlineLevelBox( final RenderBox box ) {
// todo Arabic text
if ( checkCacheValid( box ) ) {
return;
}
// The height of an inline-level box will be computed when the vertical-alignemnt is done.
if ( breakState.isActive() == false ) {
return;
}
final int nodeType = box.getLayoutNodeType();
if ( ( nodeType & LayoutNodeTypes.MASK_BOX_INLINE ) == LayoutNodeTypes.MASK_BOX_INLINE ) {
breakState.closeContext();
return;
}
final Object suspender = breakState.getSuspendItem();
if ( box.getInstanceId() == suspender ) {
breakState.setSuspendItem( null );
return;
}
if ( suspender != null ) {
return;
}
if ( nodeType == LayoutNodeTypes.TYPE_BOX_PARAGRAPH ) {
throw new IllegalStateException( "This cannot be; Why is there a paragraph inside a inline context" );
}
}
private long computeVerticalInlinePosition( final RenderNode node ) {
final RenderBox parent = node.getParent();
if ( parent != null ) {
// the computed position of an inline-element must be the same as the position of the parent element.
// A inline-box always has an other inline-box as parent (the paragraph-pool-box is the only exception;
// and this one is handled elsewhere).
// Top and bottom margins are not applied to inline-elements.
final StaticBoxLayoutProperties blp = parent.getStaticBoxLayoutProperties();
final BoxDefinition bdef = parent.getBoxDefinition();
final long insetTop = ( blp.getBorderTop() + bdef.getPaddingTop() );
return ( insetTop + parent.getCachedY() );
} else {
// there's no parent .. Should not happen, shouldn't it?
return ( 0 );
}
}
protected boolean startCanvasLevelBox( final RenderBox box ) {
if ( checkCacheValid( box ) ) {
return false;
}
performStartTable( box );
box.setCachedY( computeVerticalCanvasPosition( box ) );
if ( breakState.isActive() == false ) {
final int nodeType = box.getNodeType();
if ( nodeType == LayoutNodeTypes.TYPE_BOX_PARAGRAPH ) {
final ParagraphRenderBox paragraphBox = (ParagraphRenderBox) box;
// We cant cache that ... the shift operations later would misbehave
// One way around would be to at least store the layouted offsets
// (which should be immutable as long as the line did not change its
// contents) and to reapply them on each run. This is cheaper than
// having to compute the whole v-align for the whole line.
breakState.init( paragraphBox );
} else if ( nodeType == LayoutNodeTypes.TYPE_BOX_CONTENT ) {
final RenderableReplacedContentBox rpc = (RenderableReplacedContentBox) box;
final long computedHeight = ReplacedContentUtil.computeHeight( rpc, 0, box.getCachedWidth() );
box.setCachedHeight( computedHeight );
}
return true;
}
// No breakstate and not being suspended? Why this?
if ( breakState.isSuspended() == false ) {
throw new IllegalStateException( "This cannot be: No breakstate and not being suspended? Why this?" );
}
// this way or another - we are suspended now. So there is no need to look
// at the children anymore ..
return false;
}
protected void processCanvasLevelNode( final RenderNode node ) {
node.setCachedY( computeVerticalCanvasPosition( node ) );
if ( node.getNodeType() == LayoutNodeTypes.TYPE_NODE_FINISHEDNODE ) {
final FinishedRenderNode fnode = (FinishedRenderNode) node;
node.setCachedHeight( fnode.getLayoutedHeight() );
} else {
node.setCachedHeight( 0 );
}
}
/**
* Finishes up a canvas level box. This updates/affects the height of the parent, as the canvas model defines that the
* parent always fully encloses all of its childs.
* <p/>
* When no preferred height is defined, the height of an element is the maximum of its minimum-height and the absolute
* height of all of its direct children.
* <p/>
* To resolve the value of percentages, the system uses the maximum of the parent's height and the maximum of all (y +
* height) of all children.)
*
* @param box
* the box.
*/
protected void finishCanvasLevelBox( final RenderBox box ) {
if ( checkCacheValid( box ) ) {
return;
}
performFinishTable( box );
final int layoutNodeType = box.getLayoutNodeType();
if ( ( layoutNodeType & LayoutNodeTypes.MASK_BOX_BLOCK ) == LayoutNodeTypes.MASK_BOX_BLOCK ) {
box.setCachedHeight( computeBlockHeightAndAlign( box ) );
} else if ( ( layoutNodeType & LayoutNodeTypes.MASK_BOX_ROW ) == LayoutNodeTypes.MASK_BOX_ROW ) {
box.setCachedHeight( computeRowHeight( box, 0 ) );
} else if ( layoutNodeType == LayoutNodeTypes.TYPE_BOX_CONTENT ) {
// ignored ...
} else {
box.setCachedHeight( computeCanvasHeight( box ) );
}
if ( breakState.isActive() ) {
final Object suspender = breakState.getSuspendItem();
if ( box.getInstanceId() == suspender ) {
breakState.setSuspendItem( null );
return;
}
if ( suspender != null ) {
return;
}
if ( layoutNodeType == LayoutNodeTypes.TYPE_BOX_PARAGRAPH ) {
breakState.deinit();
}
}
}
private long computeVerticalCanvasPosition( final RenderNode node ) {
final RenderBox parent = node.getParent();
final long parentPosition;
if ( parent == null ) {
parentPosition = 0;
} else {
final StaticBoxLayoutProperties blp = parent.getStaticBoxLayoutProperties();
final BoxDefinition bdef = parent.getBoxDefinition();
final long insetsTop = ( blp.getBorderTop() + bdef.getPaddingTop() );
parentPosition = parent.getCachedY() + insetsTop;
}
final double posY = node.getNodeLayoutProperties().getPosY();
if ( node.isSizeSpecifiesBorderBox() ) {
return ( parentPosition + RenderLength.resolveLength( 0, posY ) );
} else {
final long insetsTop;
if ( ( node.getLayoutNodeType() & LayoutNodeTypes.MASK_BOX ) == LayoutNodeTypes.MASK_BOX ) {
final RenderBox box = (RenderBox) node;
final StaticBoxLayoutProperties blp = box.getStaticBoxLayoutProperties();
final BoxDefinition bdef = box.getBoxDefinition();
insetsTop = ( blp.getBorderTop() + bdef.getPaddingTop() );
} else {
insetsTop = 0;
}
return ( parentPosition + RenderLength.resolveLength( 0, posY ) - insetsTop );
}
}
private static long computeCanvasHeight( final RenderBox box ) {
final StaticBoxLayoutProperties blp = box.getStaticBoxLayoutProperties();
final BoxDefinition bdef = box.getBoxDefinition();
final BoxDefinition boxDefinition = box.getBoxDefinition();
final RenderLength minHeight = boxDefinition.getMinimumHeight();
final RenderLength preferredHeight = boxDefinition.getPreferredHeight();
final RenderLength maxHeight = boxDefinition.getMaximumHeight();
final long insetsTop = ( blp.getBorderTop() + bdef.getPaddingTop() );
final long insetsBottom = blp.getBorderBottom() + bdef.getPaddingBottom();
final long insets = insetsTop + insetsBottom;
// find the maximum of the used height (for all childs) and the specified min-height.
final long minHeightResolved = minHeight.resolve( 0 );
long consumedHeight;
if ( box.isSizeSpecifiesBorderBox() ) {
consumedHeight = Math.max( minHeightResolved, insets ) - insetsBottom;
} else {
consumedHeight = minHeightResolved + insetsTop;
}
final long boxY = box.getCachedY();
RenderNode node = box.getFirstChild();
while ( node != null ) {
final long childY2 = ( node.getCachedY() + node.getCachedHeight() );
final long childLocalY2 = childY2 - boxY;
if ( childLocalY2 > consumedHeight ) {
consumedHeight = childLocalY2;
}
node = node.getNext();
}
consumedHeight += insetsBottom;
// The consumed height computed above specifies the size at the border-edge.
// However, depending on the box-sizing property, we may have to resolve them against the
// content-edge instead.
final long maxHeightResolved = maxHeight.resolve( 0, InfiniteMajorAxisLayoutStep.MAX_AUTO );
if ( box.isSizeSpecifiesBorderBox() ) {
final long prefHeightResolved;
if ( RenderLength.AUTO.equals( preferredHeight ) ) {
prefHeightResolved = consumedHeight;
} else if ( preferredHeight.isPercentage() == false ) {
prefHeightResolved = preferredHeight.resolve( 0 );
} else {
prefHeightResolved = consumedHeight;
}
final long height = ProcessUtility.computeLength( minHeightResolved, maxHeightResolved, prefHeightResolved );
return ( height );
} else {
consumedHeight = Math.max( 0, consumedHeight - insets );
final long prefHeightResolved;
if ( RenderLength.AUTO.equals( preferredHeight ) ) {
prefHeightResolved = consumedHeight;
} else if ( preferredHeight.isPercentage() == false ) {
prefHeightResolved = preferredHeight.resolve( 0 );
} else {
prefHeightResolved = consumedHeight;
}
final long height = ProcessUtility.computeLength( minHeightResolved, maxHeightResolved, prefHeightResolved );
return ( height + insets );
}
}
protected boolean startRowLevelBox( final RenderBox box ) {
if ( checkCacheValid( box ) ) {
return false;
}
performStartTable( box );
// Compute the block-position of the box. The box is positioned relative to the previous sibling or
// relative to the parent.
box.setCachedY( computeVerticalRowPosition( box ) );
if ( breakState.isActive() ) {
// No breakstate and not being suspended? Why this?
if ( breakState.isSuspended() == false ) {
throw new IllegalStateException( "This cannot be." );
}
// this way or another - we are suspended now. So there is no need to look
// at the children anymore ..
// This code is only executed for inline-block elements. Inline-block elements are not part of
// the 0.8.9 or 1.0 engine layouting.
return false;
}
final int layoutNodeType = box.getLayoutNodeType();
if ( layoutNodeType == LayoutNodeTypes.TYPE_BOX_PARAGRAPH ) {
final ParagraphRenderBox paragraphBox = (ParagraphRenderBox) box;
// We cant cache that ... the shift operations later would misbehave
// One way around would be to at least store the layouted offsets
// (which should be immutable as long as the line did not change its
// contents) and to reapply them on each run. This is cheaper than
// having to compute the whole v-align for the whole line.
breakState.init( paragraphBox );
} else if ( layoutNodeType == LayoutNodeTypes.TYPE_BOX_CONTENT ) {
final RenderableReplacedContentBox rpc = (RenderableReplacedContentBox) box;
box.setCachedHeight( ReplacedContentUtil.computeHeight( rpc, 0, box.getCachedWidth() ) );
}
return true;
}
protected void processRowLevelNode( final RenderNode node ) {
node.setCachedY( computeVerticalRowPosition( node ) );
final int nodeType = node.getLayoutNodeType();
if ( nodeType == LayoutNodeTypes.TYPE_NODE_FINISHEDNODE ) {
final FinishedRenderNode fnode = (FinishedRenderNode) node;
node.setCachedHeight( fnode.getLayoutedHeight() );
} else if ( ( nodeType & LayoutNodeTypes.MASK_BOX_INLINE ) == LayoutNodeTypes.MASK_BOX_INLINE ) {
throw new IllegalStateException( "A Inline-Box must be contained in a paragraph." );
}
}
protected void finishRowLevelBox( final RenderBox box ) {
if ( checkCacheValid( box ) ) {
return;
}
performFinishTable( box );
final int layoutNodeType = box.getLayoutNodeType();
if ( ( layoutNodeType & LayoutNodeTypes.MASK_BOX_BLOCK ) == LayoutNodeTypes.MASK_BOX_BLOCK ) {
box.setCachedHeight( computeBlockHeightAndAlign( box ) );
} else if ( ( layoutNodeType & LayoutNodeTypes.MASK_BOX_ROW ) == LayoutNodeTypes.MASK_BOX_ROW ) {
box.setCachedHeight( computeRowHeight( box, 0 ) );
} else if ( layoutNodeType == LayoutNodeTypes.TYPE_BOX_CONTENT ) {
// ignored ..
} else {
box.setCachedHeight( computeCanvasHeight( box ) );
}
if ( breakState.isActive() ) {
final Object suspender = breakState.getSuspendItem();
if ( box.getInstanceId() == suspender ) {
breakState.setSuspendItem( null );
return;
}
if ( suspender != null ) {
return;
}
if ( layoutNodeType == LayoutNodeTypes.TYPE_BOX_PARAGRAPH ) {
breakState.deinit();
}
}
}
private long computeVerticalRowPosition( final RenderNode node ) {
final RenderBox parent = node.getParent();
if ( parent != null ) {
// the computed position of an inline-element must be the same as the position of the parent element.
// A inline-box always has an other inline-box as parent (the paragraph-pool-box is the only exception;
// and this one is handled elsewhere).
// Top and bottom margins are not applied to inline-elements.
final StaticBoxLayoutProperties blp = parent.getStaticBoxLayoutProperties();
final BoxDefinition bdef = parent.getBoxDefinition();
final long insetTop = ( blp.getBorderTop() + bdef.getPaddingTop() );
return ( insetTop + parent.getCachedY() );
} else {
// there's no parent .. Should not happen, shouldn't it?
return ( 0 );
}
}
protected boolean startTableCellLevelBox( final RenderBox box ) {
// table cells behave like block-level cells most of the time.
return startBlockLevelBox( box );
}
protected void finishTableCellLevelBox( final RenderBox box ) {
// table cells behave like block-level cells most of the time.
finishBlockLevelBox( box );
}
protected boolean startTableRowLevelBox( final RenderBox box ) {
box.setCachedY( computeVerticalRowPosition( box ) );
if ( box.getNodeType() == LayoutNodeTypes.TYPE_BOX_TABLE_CELL ) {
getTableRowHeightStep().startTableCell( (TableCellRenderBox) box );
} else {
final long blockHeight = computeTableHeightAndAlign( box );
box.setCachedHeight( blockHeight );
}
markAllChildsDirty( box );
return true;
}
protected void finishTableRowLevelBox( final RenderBox box ) {
clearAllChildsDirtyMarker( box );
if ( box.getNodeType() == LayoutNodeTypes.TYPE_BOX_TABLE_CELL ) {
final long blockHeight = computeTableHeightAndAlign( box );
getTableRowHeightStep().finishTableCell( (TableCellRenderBox) box, blockHeight );
} else {
final long blockHeight = computeTableHeightAndAlign( box );
box.setCachedHeight( blockHeight );
}
}
protected boolean startTableLevelBox( final RenderBox box ) {
box.setCachedY( computeVerticalBlockPosition( box ) );
if ( box instanceof TableSectionRenderBox ) {
getTableRowHeightStep().startTableSection( (TableSectionRenderBox) box );
}
return true;
}
protected void processTableLevelNode( final RenderNode node ) {
processBlockLevelNode( node );
}
protected void finishTableLevelBox( final RenderBox box ) {
box.setCachedHeight( computeTableHeightAndAlign( box ) );
if ( box instanceof TableSectionRenderBox ) {
getTableRowHeightStep().finishTableSection( (TableSectionRenderBox) box );
}
}
protected boolean startTableSectionLevelBox( final RenderBox box ) {
if ( box instanceof TableRowRenderBox ) {
getTableRowHeightStep().startTableRow( (TableRowRenderBox) box );
}
box.setCachedY( computeVerticalBlockPosition( box ) );
return true;
}
protected void processTableSectionLevelNode( final RenderNode node ) {
processBlockLevelNode( node );
}
protected void finishTableSectionLevelBox( final RenderBox box ) {
box.setCachedHeight( 0 );
}
}