/* * 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() ); } } }