/*
* 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 - 2009 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.CacheBoxShifter;
import org.pentaho.reporting.engine.classic.core.layout.process.InfiniteMajorAxisLayoutStep;
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();
}
}
}