/*
* 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.valign;
import org.pentaho.reporting.engine.classic.core.ElementAlignment;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderNode;
import org.pentaho.reporting.engine.classic.core.layout.process.InfiniteMajorAxisLayoutStep;
import org.pentaho.reporting.engine.classic.core.layout.process.util.CacheBoxShifter;
import org.pentaho.reporting.engine.classic.core.layout.text.ExtendedBaselineInfo;
import org.pentaho.reporting.engine.classic.core.style.VerticalTextAlign;
/**
* There's only one alignment processor for the vertical layouting. The processor is non-iterative, it receives a single
* primary sequence (which represents a line) and processes that fully.
* <p/>
* As result, this processor generates a list of offsets and heights; the offset of the outermost element is always zero
* and the height is equal to the height of the whole line.
*
* @author Thomas Morgner
*/
public final class VerticalAlignmentProcessor {
// private long lineHeight;
private long minTopPos;
private long maxBottomPos;
private BoxAlignContext rootContext;
private long sourcePosition;
private InfiniteMajorAxisLayoutStep majorAxisLayoutStep;
public VerticalAlignmentProcessor() {
}
private InfiniteMajorAxisLayoutStep getMajorAxisLayoutStep() {
if ( majorAxisLayoutStep == null ) {
majorAxisLayoutStep = new InfiniteMajorAxisLayoutStep();
}
return majorAxisLayoutStep;
}
// 7% of our time is spent here ..
public void align( final BoxAlignContext alignStructure, final long y1, final long lineHeight ) {
this.minTopPos = Long.MAX_VALUE;
this.maxBottomPos = Long.MIN_VALUE;
// this.lineHeight = lineHeight;
this.rootContext = alignStructure;
this.sourcePosition = y1;
performAlignment( alignStructure );
if ( alignStructure.isSimpleNode() == false ) {
performExtendedAlignment( alignStructure, alignStructure );
}
normalizeAlignment( alignStructure );
alignStructure.setAfterEdge( Math.max( maxBottomPos, lineHeight ) );
alignStructure.shift( -minTopPos + y1 );
apply( alignStructure );
this.rootContext = null;
}
private void performAlignment( final BoxAlignContext box ) {
// We have a valid align structure here.
AlignContext child = box.getFirstChild();
while ( child != null ) {
if ( child instanceof InlineBlockAlignContext ) {
final InlineBlockAlignContext context = (InlineBlockAlignContext) child;
final InfiniteMajorAxisLayoutStep majorAxisLayoutStep = getMajorAxisLayoutStep();
majorAxisLayoutStep.continueComputation( (RenderBox) context.getNode() );
// todo: Allow to select other than the first baseline ..
}
BoxAlignContext parent = box;
final VerticalTextAlign verticalAlignment = child.getNode().getVerticalTextAlignment();
if ( VerticalTextAlign.TOP.equals( verticalAlignment ) || VerticalTextAlign.BOTTOM.equals( verticalAlignment ) ) {
// Those alignments ignore the normal alignment rules and all boxes
// align themself on the extended linebox.
// I'm quite sure that the definition itself is unclean ..
// continue;
parent = rootContext;
}
// Now lets assume we have a valid structure...
// All childs have been aligned. Now check how this box is positioned
// in relation to its parent.
final long shiftDistance = computeShift( child, parent );
// The alignment baseline defines to which baseline of the parent we
// will align this element
final int alignmentBaseline = child.getDominantBaseline();
// The alignment adjust defines, where the alignment point of this
// child will be. The alignment adjust is relative to the child's
// line-height. In the normal case, this will be zero to indicate, that
// the alignment point is equal to the child's dominant baseline.
final long childAlignmentPoint = computeAlignmentAdjust( child, alignmentBaseline );
final long childAscent = child.getBaselineDistance( ExtendedBaselineInfo.BEFORE_EDGE );
final long childPosition = ( -childAscent + childAlignmentPoint ) + child.getBeforeEdge();
// If zero, the parent's alignment point is on the parent's dominant
// baseline.
final long parentAlignmentPoint = parent.getBaselineDistance( alignmentBaseline );
final long parentAscent = parent.getBaselineDistance( ExtendedBaselineInfo.BEFORE_EDGE );
final long parentPosition = ( -parentAscent + parentAlignmentPoint ) + parent.getBeforeEdge();
final long alignment = parentPosition - childPosition;
final long offset = shiftDistance + alignment;
child.shift( offset );
if ( rootContext.getBeforeEdge() > child.getBeforeEdge() ) {
rootContext.setBeforeEdge( child.getBeforeEdge() );
}
if ( rootContext.getAfterEdge() < child.getAfterEdge() ) {
rootContext.setAfterEdge( child.getAfterEdge() );
}
if ( child instanceof BoxAlignContext ) {
performAlignment( (BoxAlignContext) child );
}
child = child.getNext();
}
}
/**
* This simply searches the maximum shift that we have to do to normalize the element.
*
* @param box
* @return
*/
private void normalizeAlignment( final BoxAlignContext box ) {
minTopPos = Math.min( minTopPos, box.getBeforeEdge() );
maxBottomPos = Math.max( maxBottomPos, box.getAfterEdge() );
if ( box.isSimpleNode() ) {
return;
}
AlignContext child = box.getFirstChild();
while ( child != null ) {
if ( child instanceof BoxAlignContext ) {
normalizeAlignment( (BoxAlignContext) child );
}
child = child.getNext();
}
}
private long computeShift( final AlignContext child, final BoxAlignContext box ) {
// for now, we do not perform any advanced layouting. Maybe later ..
return 0;
}
private long computeAlignmentAdjust( final AlignContext context, final int defaultBaseLine ) {
// for now, we do not perform any advanced layouting. Maybe later ..
return context.getBaselineDistance( defaultBaseLine );
}
private void apply( final BoxAlignContext box ) {
final RenderNode node = box.getNode();
final long beforeEdge = box.getBeforeEdge();
node.setCachedY( beforeEdge );
node.setCachedHeight( box.getAfterEdge() - beforeEdge );
if ( box.isSimpleNode() ) {
AlignContext child = box.getFirstChild();
while ( child != null ) {
if ( child instanceof BoxAlignContext ) {
apply( (BoxAlignContext) child );
} else {
final RenderNode childNode = child.getNode();
final long childBeforeEdge = child.getBeforeEdge();
childNode.setCachedY( childBeforeEdge );
childNode.setCachedHeight( child.getAfterEdge() - childBeforeEdge );
}
child = child.getNext();
}
} else {
AlignContext child = box.getFirstChild();
while ( child != null ) {
if ( child instanceof BoxAlignContext ) {
apply( (BoxAlignContext) child );
} else if ( child instanceof InlineBlockAlignContext ) {
// Luckily the layoutmodel does not yet specify inline-boxes. Need to be fixed in the flow-engine.
// also shift all the childs.
final long shift = child.getBeforeEdge() - sourcePosition;
CacheBoxShifter.shiftBox( child.getNode(), shift );
} else {
final RenderNode childNode = child.getNode();
final long childBeforeEdge = child.getBeforeEdge();
childNode.setCachedY( childBeforeEdge );
childNode.setCachedHeight( child.getAfterEdge() - childBeforeEdge );
}
child = child.getNext();
}
}
}
// protected static void print (final BoxAlignContext alignContext, final int level)
// {
// Log.debug ("Box: L:" + level + " Y1:" + alignContext.getBeforeEdge() +
// " Y2:" + alignContext.getAfterEdge() +
// " H:" + (alignContext.getAfterEdge() - alignContext.getBeforeEdge())
// );
// // We have a valid align structure here.
// AlignContext child = alignContext.getFirstChild();
// while (child != null)
// {
// if (child instanceof BoxAlignContext)
// {
// print((BoxAlignContext) child, level + 1);
// }
// else
// {
// Log.debug ("...: L:" + level + " Y1:" + child.getBeforeEdge() +
// " Y2:" + (child.getAfterEdge()) +
// " H:" + (child.getAfterEdge() - child.getBeforeEdge()));
// }
// child = child.getNext();
// }
// }
//
/**
* Verify all elements with alignment top or bottom. This step is required, as the extended linebox is allowed to
* change its height during the ordinary alignment. Argh, I hate that specificiation.
*
* @param box
*/
private void performExtendedAlignment( final BoxAlignContext box, final BoxAlignContext lineBox ) {
// Aligns elements with vertical-align TOP and vertical-align BOTTOM
AlignContext child = box.getFirstChild();
while ( child != null ) {
final ElementAlignment verticalAlignment = child.getNode().getNodeLayoutProperties().getVerticalAlignment();
if ( ElementAlignment.TOP.equals( verticalAlignment ) ) {
final long childTopEdge = child.getBeforeEdge();
final long parentTopEdge = lineBox.getBeforeEdge();
child.shift( parentTopEdge - childTopEdge );
} else if ( ElementAlignment.BOTTOM.equals( verticalAlignment ) ) {
// Align the childs after-edge with the parent's after-edge
final long childBottomEdge = child.getAfterEdge();
final long parentBottomEdge = lineBox.getAfterEdge();
child.shift( parentBottomEdge - childBottomEdge );
}
if ( child instanceof BoxAlignContext ) {
performExtendedAlignment( (BoxAlignContext) child, lineBox );
}
child = child.getNext();
}
}
}