/*
* 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.alignment;
import org.pentaho.reporting.engine.classic.core.layout.model.LayoutNodeTypes;
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.layoutrules.EndSequenceElement;
import org.pentaho.reporting.engine.classic.core.layout.process.layoutrules.InlineBoxSequenceElement;
import org.pentaho.reporting.engine.classic.core.layout.process.layoutrules.InlineSequenceElement;
import org.pentaho.reporting.engine.classic.core.layout.process.layoutrules.StartSequenceElement;
import org.pentaho.reporting.engine.classic.core.layout.process.layoutrules.TextSequenceElement;
import org.pentaho.reporting.engine.classic.core.util.LongList;
/**
* Performs the left-alignment computations.
* <p/>
* The inf-min-step creates the initial sequence of elements. The alignment processor now iterates over the sequence and
* produces the layouted line.
* <p/>
* Elements can be split, splitting is a local operation and does not copy the children. Text splitting may produce a
* totally different text (see: TeX hyphenation system).
* <p/>
* The process is iterative and continues unless all elements have been consumed.
*
* @author Thomas Morgner
*/
public final class LeftAlignmentProcessor extends AbstractAlignmentProcessor
{
private long position;
private int pageSegment;
public LeftAlignmentProcessor()
{
}
public int getPageSegment()
{
return pageSegment;
}
public void setPageSegment(final int pageSegment)
{
this.pageSegment = pageSegment;
}
private long getPosition()
{
return position;
}
private void setPosition(final long position)
{
this.position = position;
}
private void addPosition(final long width)
{
this.position += width;
}
public RenderNode next()
{
position = getStartOfLine();
pageSegment = 0;
final RenderNode retval = super.next();
position = 0;
pageSegment = 0;
return retval;
}
public void performLastLineAlignment()
{
position = getStartOfLine();
pageSegment = 0;
super.performLastLineAlignment();
position = 0;
pageSegment = 0;
}
/**
* Handle the next input chunk.
*
* @param start the start index
* @param count the number of elements in the sequence
* @return the index of the last element that will fit on the current line.
*/
protected int handleElement(final int start, final int count)
{
final InlineSequenceElement[] sequenceElements = getSequenceElements();
final RenderNode[] nodes = getNodes();
final long[] elementDimensions = getElementDimensions();
final long[] elementPositions = getElementPositions();
long width = 0;
final int endIndex = start + count;
// In the given range, there should be only one content element.
InlineSequenceElement contentElement = null;
int contentIndex = start;
for (int i = start; i < endIndex; i++)
{
final InlineSequenceElement element = sequenceElements[i];
final RenderNode node = nodes[i];
if (element instanceof StartSequenceElement ||
element instanceof EndSequenceElement)
{
width += element.getMaximumWidth(node);
continue;
}
width += element.getMaximumWidth(node);
contentElement = element;
contentIndex = i;
}
final long nextPosition = getPosition() + width;
final long lastPageBreak = getPageBreak(getPagebreakCount() - 1);
// Do we cross a page boundary?
if (nextPosition > lastPageBreak)
{
// On outer break: Stop processing
// Dont write through to the stored position; but prepare if
// we have to fallback ..
long position = getPosition();
for (int i = start; i < endIndex; i++)
{
final InlineSequenceElement element = sequenceElements[i];
final RenderNode node = nodes[i];
elementPositions[i] = position;
final long elementWidth = element.getMaximumWidth(node);
elementDimensions[i] = elementWidth;
position += elementWidth;
}
// we cross a pagebreak. Stop working on it - we bail out here.
if (contentElement instanceof TextSequenceElement)
{
// the element may be splittable. Test, and if so, give a hint to the
// outside world ..
setSkipIndex(endIndex);
setBreakableIndex(contentIndex);
return (start);
}
// This is the first element and it still does not fit. How evil.
if (start == 0)
{
if (contentElement instanceof InlineBoxSequenceElement)
{
final RenderNode node = nodes[contentIndex];
if ((node.getNodeType() & LayoutNodeTypes.MASK_BOX) == LayoutNodeTypes.MASK_BOX)
{
// OK, limit the size of the box to the maximum line width and
// revalidate it.
final long contentPosition = elementPositions[contentIndex];
final RenderBox box = (RenderBox) node;
final long maxWidth = (getEndOfLine() - contentPosition);
computeInlineBlock(box, contentPosition, maxWidth);
elementDimensions[endIndex - 1] = node.getCachedWidth();
}
}
setSkipIndex(endIndex);
}
return (start);
}
final long innerPagebreak = getPageBreak(getPageSegment());
if (nextPosition > innerPagebreak)
{
// It is an inner pagebreak and the current element would not fit into the remaining space.
// Move the element to the next page segment (but only if the start is not on
setPosition(innerPagebreak);
setPageSegment(getPageSegment() + 1);
}
// No, it is an ordinary advance ..
// Check, whether we hit an item-sequence element
if (contentElement instanceof InlineBoxSequenceElement == false)
{
for (int i = start; i < endIndex; i++)
{
final RenderNode node = nodes[i];
final InlineSequenceElement element = sequenceElements[i];
elementPositions[i] = getPosition();
final long elementWidth = element.getMaximumWidth(node);
elementDimensions[i] = elementWidth;
addPosition(elementWidth);
}
return endIndex;
}
// Handle the ItemSequence element.
// This is a bit more complicated. So we encountered an inline-block
// element here. That means, the element will try to occuppy its
// maximum-content-width.
// Log.debug("Advance block at index " + contentIndex);
// final long ceWidth = contentElement.getMinimumWidth();
// final long extraSpace = contentElement.getMaximumWidth();
// Log.debug("Advance block: Min " + ceWidth);
// Log.debug("Advance block: Max " + extraSpace);
final RenderNode contentNode = nodes[contentIndex];
final long itemElementWidth = contentElement.getMaximumWidth(contentNode);
if ((contentNode.getNodeType() & LayoutNodeTypes.MASK_BOX) == LayoutNodeTypes.MASK_BOX)
{
final RenderBox box = (RenderBox) contentNode;
computeInlineBlock(box, getPosition(), itemElementWidth);
}
else
{
contentNode.setCachedX(getPosition());
contentNode.setCachedWidth(itemElementWidth);
}
final long preferredEndingPos = getPosition() + itemElementWidth;
if (preferredEndingPos > getEndOfLine())
{
// We would eat the whole space up to the end of the line and more
// So lets move that element to the next line instead...
// But: We could easily end in an endless loop here. So check whether
// the element is the first in the line
if (start == 0)
{
// As it is guaranteed, that each chunk contains at least one item,
// checking for start == 0 is safe enough ..
return endIndex;
}
return start;
}
for (int i = start; i < contentIndex; i++)
{
final InlineSequenceElement element = sequenceElements[i];
final RenderNode node = nodes[contentIndex];
final long elementWidth = element.getMaximumWidth(node);
elementPositions[i] = getPosition();
elementDimensions[i] = elementWidth;
addPosition(elementWidth);
}
elementPositions[contentIndex] = getPosition();
elementDimensions[contentIndex] = itemElementWidth;
setPosition(preferredEndingPos);
for (int i = contentIndex + 1; i < endIndex; i++)
{
final InlineSequenceElement element = sequenceElements[i];
final RenderNode node = nodes[contentIndex];
final long elementWidth = element.getMaximumWidth(node);
elementPositions[i] = getPosition();
elementDimensions[i] = elementWidth;
addPosition(elementWidth);
}
return endIndex;
}
public void performSkipAlignment(final int endIndex)
{
// this is a NO-OP method, as the skip-alignment is simply a left-alignment ...
}
protected void updateBreaksForLastLineAlignment()
{
final long[] horizontalBreaks = getPageGrid().getHorizontalBreaks();
final int breakCount = horizontalBreaks.length;
final LongList pageLongList = new LongList(breakCount);
final long endOfLine = getEndOfLine();
final long startOfLine = getStartOfLine();
for (int i = 0; i < breakCount; i++)
{
final long pos = horizontalBreaks[i];
if (pos <= startOfLine)
{
// skip ..
continue;
}
if (pos >= endOfLine)
{
break;
}
pageLongList.add(pos);
}
//pageLongList.add(endOfLine);
pageLongList.add(Long.MAX_VALUE);
final long[] pagebreaks = getPageBreaks();
updatePageBreaks(pageLongList.toArray(pagebreaks), pageLongList.size());
}
}