/*
* 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.ClassicEngineBoot;
import org.pentaho.reporting.engine.classic.core.function.ProcessingContext;
import org.pentaho.reporting.engine.classic.core.layout.model.Border;
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.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.RenderableComplexText;
import org.pentaho.reporting.engine.classic.core.layout.model.context.BoxDefinition;
import org.pentaho.reporting.engine.classic.core.layout.model.context.NodeLayoutProperties;
import org.pentaho.reporting.engine.classic.core.layout.model.context.StaticBoxLayoutProperties;
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.StaticChunkWidthUpdate;
import org.pentaho.reporting.engine.classic.core.layout.process.util.StaticChunkWidthUpdatePool;
import org.pentaho.reporting.engine.classic.core.layout.process.util.StaticRootChunkWidthUpdate;
import org.pentaho.reporting.engine.classic.core.layout.text.ExtendedBaselineInfo;
import org.pentaho.reporting.engine.classic.core.style.BandStyleKeys;
import org.pentaho.reporting.engine.classic.core.style.ElementStyleKeys;
import org.pentaho.reporting.engine.classic.core.style.StyleSheet;
import org.pentaho.reporting.engine.classic.core.style.TextStyleKeys;
import org.pentaho.reporting.engine.classic.core.style.WhitespaceCollapse;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictGeomUtility;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
/**
* Computes the width for all elements. This uses the CSS algorithm, percentages are resolved against the parent's
* already known width.
*
* @author Thomas Morgner
*/
public final class ComputeStaticPropertiesProcessStep extends IterateSimpleStructureProcessStep {
// Set the maximum height to an incredibly high value. This is now 2^43 micropoints or more than
// 3000 kilometers. Please call me directly at any time if you need more space for printing.
public static final long MAX_AUTO = StrictGeomUtility.toInternalValue( 0x80000000000L );
private static final StaticRootChunkWidthUpdate ROOT = new StaticRootChunkWidthUpdate();
private OutputProcessorMetaData metaData;
private ResourceManager resourceManager;
private boolean overflowXSupported;
private boolean overflowYSupported;
private boolean widowsEnabled;
private StaticChunkWidthUpdate chunkWidthUpdate;
private StaticChunkWidthUpdatePool chunkWidthUpdatePool;
private boolean widowOrphanDefinitionsEncountered;
private boolean designTime;
public ComputeStaticPropertiesProcessStep() {
chunkWidthUpdatePool = new StaticChunkWidthUpdatePool();
}
public void initialize( final OutputProcessorMetaData metaData, final ProcessingContext processingContext ) {
this.metaData = metaData;
this.overflowXSupported = metaData.isFeatureSupported( OutputProcessorFeature.ASSUME_OVERFLOW_X );
this.overflowYSupported = metaData.isFeatureSupported( OutputProcessorFeature.ASSUME_OVERFLOW_Y );
this.widowsEnabled = !ClassicEngineBoot.isEnforceCompatibilityFor( processingContext.getCompatibilityLevel(), 3, 8 );
this.widowOrphanDefinitionsEncountered = false;
this.designTime = metaData.isFeatureSupported( OutputProcessorFeature.DESIGNTIME );
this.resourceManager = processingContext.getResourceManager();
}
public boolean isWidowOrphanDefinitionsEncountered() {
return widowOrphanDefinitionsEncountered;
}
public void compute( final LogicalPageBox root ) {
this.chunkWidthUpdate = ROOT;
startProcessing( root );
this.chunkWidthUpdate = null;
}
protected boolean startBox( final RenderBox box ) {
final long changeTracker = box.getChangeTracker();
final long age = box.getStaticBoxPropertiesAge();
if ( changeTracker == age ) {
return false;
}
this.chunkWidthUpdate = createChunkWidthUpdate( box );
updateStaticProperties( box );
computeWidowOrphanIndicator( box );
if ( box.getNodeType() == LayoutNodeTypes.TYPE_BOX_PARAGRAPH ) {
processParagraphChilds( (ParagraphRenderBox) box );
return false;
}
return true;
}
protected void finishBox( final RenderBox box ) {
final long changeTracker = box.getChangeTracker();
final long age = box.getStaticBoxPropertiesAge();
if ( changeTracker == age ) {
return;
}
updateMinimumChunkWidth( box );
}
protected void processOtherNode( final RenderNode node ) {
if ( node instanceof RenderableComplexText ) {
RenderableComplexText t = (RenderableComplexText) node;
t.computeMinimumChunkWidth( metaData, resourceManager );
}
chunkWidthUpdate.update( node.getMinimumChunkWidth() );
}
protected void processParagraphChilds( final ParagraphRenderBox box ) {
final ExtendedBaselineInfo extendedBaselineInfo = box.getStaticBoxLayoutProperties().getNominalBaselineInfo();
if ( extendedBaselineInfo == null ) {
throw new IllegalStateException( "Baseline info must not be null at this point" );
}
final StyleSheet styleSheet = box.getNodeLayoutProperties().getStyleSheet();
final double value = styleSheet.getDoubleStyleProperty( TextStyleKeys.LINEHEIGHT, 0 );
final long afterEdge = extendedBaselineInfo.getBaseline( ExtendedBaselineInfo.AFTER_EDGE );
if ( value <= 0 ) {
box.getPool().setLineHeight( afterEdge );
} else {
box.getPool().setLineHeight( RenderLength.resolveLength( afterEdge, value ) );
}
startProcessing( box.getPool() );
}
private void computeBreakIndicator( final RenderBox box ) {
final StyleSheet styleSheet = box.getStyleSheet();
final RenderBox parent = box.getParent();
if ( parent != null ) {
final boolean breakBefore = styleSheet.getBooleanStyleProperty( BandStyleKeys.PAGEBREAK_BEFORE );
final boolean breakAfter = box.isBreakAfter();
final int nodeType = parent.getLayoutNodeType();
if ( ( breakBefore ) && ( nodeType != LayoutNodeTypes.TYPE_BOX_PARAGRAPH ) ) {
box.setManualBreakIndicator( RenderBox.BreakIndicator.DIRECT_MANUAL_BREAK );
applyIndirectManualBreakIndicator( parent );
return;
}
if ( breakAfter && ( nodeType != LayoutNodeTypes.TYPE_BOX_PARAGRAPH ) ) {
applyIndirectManualBreakIndicator( parent );
}
}
final boolean fixedPosition =
RenderLength.AUTO.equals( styleSheet.getStyleProperty( BandStyleKeys.FIXED_POSITION, RenderLength.AUTO ) ) == false;
if ( fixedPosition ) {
applyIndirectManualBreakIndicator( box );
} else {
box.setManualBreakIndicator( RenderBox.BreakIndicator.NO_MANUAL_BREAK );
}
}
private void applyIndirectManualBreakIndicator( RenderBox node ) {
while ( node != null ) {
if ( node.getManualBreakIndicator() != RenderBox.BreakIndicator.NO_MANUAL_BREAK ) {
return;
}
node.setManualBreakIndicator( RenderBox.BreakIndicator.INDIRECT_MANUAL_BREAK );
node = node.getParent();
}
}
/**
* Collects and possibly computes the static properties according to the CSS layouting model. The classic JFreeReport
* layout model does not know anything about margins or borders, so in that case resolving against the CSS model is
* ok.
*
* @param box
* the box that should be processed.
* @return true if the box is new, false otherwise
*/
private void updateStaticProperties( final RenderBox box ) {
final BoxDefinition boxDefinition = box.getBoxDefinition();
final StaticBoxLayoutProperties sblp = box.getStaticBoxLayoutProperties();
if ( sblp.isBaselineCalculated() ) {
// mark box as seen ..
return;
}
computeMarginsAndBorders( box, boxDefinition, sblp );
computeResolvedStyleProperties( box, sblp );
computeBreakIndicator( box );
}
private void computeWidowOrphanIndicator( final RenderBox box ) {
final RenderBox parent = box.getParent();
if ( parent == null ) {
box.setParentWidowContexts( 0 );
return;
}
if ( widowsEnabled == false ) {
return;
}
final StaticBoxLayoutProperties sblp = parent.getStaticBoxLayoutProperties();
if ( sblp.getOrphans() > 0 || sblp.getWidows() > 0 || sblp.isAvoidPagebreakInside() ) {
widowOrphanDefinitionsEncountered = true;
box.setParentWidowContexts( parent.getParentWidowContexts() + 1 );
} else {
box.setParentWidowContexts( parent.getParentWidowContexts() );
}
}
private void computeResolvedStyleProperties( final RenderBox box, final StaticBoxLayoutProperties sblp ) {
final StyleSheet style = box.getStyleSheet();
NodeLayoutProperties nlp = box.getNodeLayoutProperties();
if ( designTime ) {
// at design-time elements can be generated that are not visible in the final output
// the report designer needs them to create a smooth design experience.
RenderBox parent = box.getParent();
if ( parent == null ) {
nlp.setVisible( style.getBooleanStyleProperty( ElementStyleKeys.VISIBLE ) );
} else if ( parent.isEmptyNodesHaveSignificance() == false ) {
nlp.setVisible( style.getBooleanStyleProperty( ElementStyleKeys.VISIBLE ) );
}
}
final int nodeType = box.getLayoutNodeType();
if ( ( nodeType & LayoutNodeTypes.MASK_BOX_INLINE ) == LayoutNodeTypes.MASK_BOX_INLINE ) {
sblp.setAvoidPagebreakInside( true );
} else if ( nodeType == LayoutNodeTypes.TYPE_BOX_TABLE_ROW || nodeType == LayoutNodeTypes.TYPE_BOX_ROWBOX ) {
sblp.setAvoidPagebreakInside( style.getBooleanStyleProperty( ElementStyleKeys.AVOID_PAGEBREAK_INSIDE, true ) );
} else {
sblp.setAvoidPagebreakInside( style.getBooleanStyleProperty( ElementStyleKeys.AVOID_PAGEBREAK_INSIDE, false ) );
}
sblp.setDominantBaseline( -1 );
if ( widowsEnabled ) {
sblp.setOrphans( style.getIntStyleProperty( ElementStyleKeys.ORPHANS, 0 ) );
sblp.setWidows( style.getIntStyleProperty( ElementStyleKeys.WIDOWS, 0 ) );
final boolean orphanOptOut = style.getBooleanStyleProperty( ElementStyleKeys.WIDOW_ORPHAN_OPT_OUT, true );
sblp.setWidowOrphanOptOut( orphanOptOut );
}
final ExtendedBaselineInfo baselineInfo = metaData.getBaselineInfo( 'x', style );
if ( baselineInfo == null ) {
throw new IllegalStateException();
}
sblp.setNominalBaselineInfo( baselineInfo );
sblp.setFontFamily( metaData.getNormalizedFontFamilyName( (String) style.getStyleProperty( TextStyleKeys.FONT ) ) );
final Object collapse = style.getStyleProperty( TextStyleKeys.WHITE_SPACE_COLLAPSE );
sblp.setPreserveSpace( WhitespaceCollapse.PRESERVE.equals( collapse ) );
sblp.setOverflowX( style.getBooleanStyleProperty( ElementStyleKeys.OVERFLOW_X, overflowXSupported ) );
sblp.setOverflowY( style.getBooleanStyleProperty( ElementStyleKeys.OVERFLOW_Y, overflowYSupported ) );
sblp.setInvisibleConsumesSpace( style.getBooleanStyleProperty( ElementStyleKeys.INVISIBLE_CONSUMES_SPACE,
nodeType == LayoutNodeTypes.TYPE_BOX_ROWBOX ) );
sblp.setVisible( style.getBooleanStyleProperty( ElementStyleKeys.VISIBLE ) );
final RenderBox parent = box.getParent();
if ( parent != null && style.getDoubleStyleProperty( ElementStyleKeys.MIN_WIDTH, 0 ) == 0
&& style.getDoubleStyleProperty( ElementStyleKeys.WIDTH, 0 ) == 0 ) {
// todo: Should that flag also take paddings and borders of the parent into account?
// They alter the available space for the childs, and thus it would make sense to establish a new
// context for resolving percentage-widths
// only a box with a parent can try to inherit a context ..
if ( ( parent.getLayoutNodeType() & LayoutNodeTypes.TYPE_BOX_BLOCK ) == LayoutNodeTypes.TYPE_BOX_BLOCK ) {
// a block level box always creates a block-context.
sblp.setDefinedWidth( true );
} else {
sblp.setDefinedWidth( false );
}
} else {
sblp.setDefinedWidth( true );
}
}
private void computeMarginsAndBorders( final RenderBox box, final BoxDefinition boxDefinition,
final StaticBoxLayoutProperties sblp ) {
final int nodeType = box.getLayoutNodeType();
if ( nodeType == LayoutNodeTypes.TYPE_BOX_TABLE_ROW || nodeType == LayoutNodeTypes.TYPE_BOX_TABLE_SECTION ) {
sblp.setBorderBottom( 0 );
sblp.setBorderTop( 0 );
sblp.setBorderRight( 0 );
sblp.setBorderLeft( 0 );
} else {
final Border border = boxDefinition.getBorder();
sblp.setBorderTop( border.getTop().getWidth() );
sblp.setBorderLeft( border.getLeft().getWidth() );
sblp.setBorderBottom( border.getBottom().getWidth() );
sblp.setBorderRight( border.getRight().getWidth() );
}
}
private StaticChunkWidthUpdate createChunkWidthUpdate( final RenderBox box ) {
if ( chunkWidthUpdate.isInline() ) {
return chunkWidthUpdatePool.createInline( chunkWidthUpdate, box );
}
final int nodeType = box.getLayoutNodeType();
if ( nodeType == LayoutNodeTypes.TYPE_BOX_PARAGRAPH ) {
return chunkWidthUpdatePool.createParagraph( chunkWidthUpdate, (ParagraphRenderBox) box );
}
if ( nodeType == LayoutNodeTypes.TYPE_BOX_ROWBOX || nodeType == LayoutNodeTypes.TYPE_BOX_TABLE_ROW ) {
return chunkWidthUpdatePool.createHorizontal( chunkWidthUpdate, box );
}
return chunkWidthUpdatePool.createVertical( chunkWidthUpdate, box );
}
protected void updateMinimumChunkWidth( final RenderBox box ) {
final long changeTracker = box.getChangeTracker();
final long age = box.getStaticBoxPropertiesAge();
if ( changeTracker == age ) {
// update the parent
if ( box.isVisible() ) {
chunkWidthUpdate.update( box.getMinimumChunkWidth() );
}
return;
}
box.setStaticBoxPropertiesAge( box.getChangeTracker() );
final StaticChunkWidthUpdate boxUpdate = chunkWidthUpdate;
boxUpdate.finish();
chunkWidthUpdate = chunkWidthUpdate.pop();
if ( box.isVisible() ) {
chunkWidthUpdate.update( box.getMinimumChunkWidth() );
}
}
}