/* * 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.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.model.SplittableRenderNode; import org.pentaho.reporting.engine.classic.core.layout.process.layoutrules.InlineBoxSequenceElement; import org.pentaho.reporting.engine.classic.core.layout.process.layoutrules.InlineSequenceElement; /** * Right alignment strategy. Not working yet, as this is unimplemented right now. * * @author Thomas Morgner */ public final class CenterAlignmentProcessor extends AbstractAlignmentProcessor { public CenterAlignmentProcessor() { } 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(); // if we reached that method, then this means, that the elements may fit // into the available space. (Assuming that there is no inner pagebreak; // a thing we do not handle yet) final int endIndex = start + count; long usedWidth = 0; long usedWidthToStart = 0; int contentIndex = start; InlineSequenceElement contentElement = null; for ( int i = 0; i < endIndex; i++ ) { final InlineSequenceElement element = sequenceElements[i]; final RenderNode node = nodes[i]; usedWidth += element.getMaximumWidth( node ); if ( i < start ) { usedWidthToStart += element.getMaximumWidth( node ); } if ( isBorderMarker( element ) ) { continue; } contentElement = element; contentIndex = i; } final long nextPosition = ( getStartOfLine() + usedWidth ); final long lastPageBreak = getPageBreak( getPagebreakCount() - 1 ); if ( nextPosition > lastPageBreak ) { // The contents we processed so far will not fit on the current line. That's dangerous. // We have to center align the content up to the start position. performCenterAlignment( start, usedWidthToStart, sequenceElements, nodes, elementDimensions, elementPositions ); // we cross a pagebreak. Stop working on it - we bail out here. if ( nodes[contentIndex] instanceof SplittableRenderNode ) { // the element may be splittable. Test, and if so, give a hint to the // outside world .. setSkipIndex( endIndex ); setBreakableIndex( contentIndex ); setBreakableMaxAllowedWidth( nextPosition - lastPageBreak ); 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 ); } // if we reached that method, then this means, that the elements may fit // into the available space. (Assuming that there is no inner pagebreak; // a thing we do not handle yet) if ( performCenterAlignment( endIndex, usedWidth, sequenceElements, nodes, elementDimensions, elementPositions ) ) { return endIndex; } return start; } private boolean performCenterAlignment( final int endIndex, final long usedWidth, final InlineSequenceElement[] sequenceElements, final RenderNode[] nodes, final long[] elementDimensions, final long[] elementPositions ) { final long startOfLine = getStartOfLine(); final long totalWidth = getEndOfLine() - startOfLine; final long emptySpace = Math.max( 0, ( totalWidth - usedWidth ) ); long position = startOfLine + emptySpace / 2; // first, make a very simple distribution of the text over all the space, and ignore the pagebreaks for ( int i = 0; i < endIndex; i++ ) { final RenderNode node = nodes[i]; final long elementWidth = sequenceElements[i].getMaximumWidth( node ); elementDimensions[i] = elementWidth; elementPositions[i] = position; position += elementWidth; } // If this does not span over multiple pages, we are finished now. // in case the centered text is larger than the available space, we fall back to left-alignment later if ( getPagebreakCount() == 1 ) { return true; } // Now search the element at the center-point. // Find the center-point of the element and the center point (and center element) of the elements. final long centerPoint = startOfLine + totalWidth / 2; final int centerPageSegment = findStartOfPageSegmentForPosition( centerPoint ); final int centerPageSegmentNext = Math.min( getPagebreakCount() - 1, centerPageSegment + 1 ); final long centerPageSegmentStart = getPageBreak( centerPageSegment ); final int leftShiftEndIndex; final int rightShiftStartIndex; if ( centerPageSegmentStart == centerPoint ) { // case 1: The center point sits directly on a pagebreak. This means, we shift the element touching the center // point to the left; and everything else is shifted to the right. final int centerElement = findElementLeftOfPosition( centerPoint, endIndex ); final long centerElementPosition = elementPositions[centerElement]; final long centerElementEnd = centerElementPosition + elementDimensions[centerElement]; if ( ( centerPoint - centerElementPosition ) > ( centerElementEnd - centerPoint ) ) { leftShiftEndIndex = centerElement + 1; rightShiftStartIndex = centerElement + 1; } else { leftShiftEndIndex = centerElement; rightShiftStartIndex = centerElement; } } else { // the end-of-line is always included in the page-break-pos array. final int endOfLineSegment = getPagebreakCount() - 1; final int startOfLineSegment = 0; if ( centerPageSegment > startOfLineSegment ) { final int leftElement = findElementLeftOfPosition( centerPageSegmentStart, endIndex ); final long elementPosition = elementPositions[leftElement]; final long elementEnd = elementPosition + elementDimensions[leftElement]; if ( elementEnd < centerPageSegmentStart ) { // if the element found fully fits on the left-hand area, include it in the shift to the left leftShiftEndIndex = leftElement + 1; } else { // otherwise shift it to the right leftShiftEndIndex = leftElement; } } else { leftShiftEndIndex = 0; } if ( centerPageSegment < endOfLineSegment ) { // we also have some elements that need to be shifted to the right. final long centerPageSegmentEnd = getPageBreak( centerPageSegmentNext ); final int rightElement = findElementLeftOfPosition( centerPageSegmentEnd, endIndex ); final long elementPosition = elementPositions[rightElement]; final long elementEnd = elementPosition + elementDimensions[rightElement]; if ( elementEnd < centerPageSegmentEnd ) { // if the element found fully fits on the left-hand area, include it in the shift to the left rightShiftStartIndex = rightElement + 1; } else { // otherwise shift it to the right rightShiftStartIndex = rightElement; } } else { rightShiftStartIndex = endIndex; } } // The distance between start of the element and the center point is greater // than the distance between the center point and the end of the element, then shift the center element // to the left. Also shift it to the left, if the element is the only element that should be centered. final long[] savedElementPos = (long[]) elementPositions.clone(); // The center-element will be shifted to the right. if ( performShiftLeft( leftShiftEndIndex, centerPageSegment, savedElementPos ) && performShiftRight( rightShiftStartIndex, endIndex, centerPageSegmentNext, savedElementPos ) ) { System.arraycopy( savedElementPos, 0, elementPositions, 0, savedElementPos.length ); return true; } return false; } private boolean performShiftRight( final int firstElementIndex, final int lastElementIndex, int segment, final long[] elementPositions ) { if ( firstElementIndex >= lastElementIndex ) { // nothing to do here .. return true; } final long[] elementDimensions = getElementDimensions(); final long endOfLine = getEndOfLine(); // We dont need the start of the center-segment, we need the end of it. // int segment = findStartOfPageSegmentForPosition(centerPoint) + 1; final int pagebreakCount = getPagebreakCount(); // prevent crash. if ( segment >= pagebreakCount ) { // Indicate that the element will not fit. More correct: the findStart.. method returned the // last segment of the page. There is no space to shift anything to the right .. return false; } long segmentEnd = getPageBreak( segment ); long segmentStart = getStartOfSegment( segment ); for ( int i = firstElementIndex; i < lastElementIndex; i++ ) { final long elementWidth = elementDimensions[i]; long elementEnd = segmentStart + elementWidth; if ( elementEnd > endOfLine ) { // this element will not fit .. return false; } // make a while a if so that we shift the element only once. This results in a slightly better laoyout if ( ( ( segment + 1 ) < pagebreakCount ) && ( elementEnd > segmentEnd ) ) { // as long as there are more segments where we could shift the element to and as long as the // element does not fit into the current segment // try the next segment .. segment += 1; segmentStart = segmentEnd; segmentEnd = getPageBreak( segment ); elementEnd = segmentStart + elementWidth; } if ( elementEnd > endOfLine ) { // the element will not fit into any of the remaining segments. So skip it. return false; } elementPositions[i] = segmentStart; segmentStart = elementEnd; } return true; } private boolean performShiftLeft( final int lastElementIndex, int segment, final long[] elementPositions ) { if ( lastElementIndex == 0 ) { // there is nothing to shift here .. return true; } // This code only fires if we distribute an element over more than a single page segment. // The text that is left of the center segment will be shifted to the left and right-aligned there. // The "centerPoint specifies the center of the element and therefore defines which segment is // considered the center-segment. // we will work on a clone, so that the undo is easier .. final long[] elementDimensions = getElementDimensions(); final int elementIdx = lastElementIndex - 1; // iterate backwards; start from the center element and right align all previous elements .. final long startOfLine = getStartOfLine(); // the current segment. // // int segment = findStartOfPageSegmentForPosition(centerPoint); long segmentEnd = getPageBreak( segment ); long segmentStart = getStartOfSegment( segment ); for ( int i = elementIdx; i >= 0; i-- ) { final long elementWidth = elementDimensions[i]; long elementStart = segmentEnd - elementWidth; if ( elementStart < startOfLine ) { // this element will not fit. Skip it. return false; } while ( segment > 0 && elementStart < segmentStart ) { // the element will not fit into the current segment. Move it to the next segment. elementStart = segmentStart - elementWidth; segment -= 1; segmentStart = getStartOfSegment( segment ); } if ( elementStart < segmentStart ) { // the element will not fit into any of the remaining segments. So skip it. return false; } elementPositions[i] = elementStart; segmentEnd = elementStart; } // Commit the changes .. return true; } private long getStartOfSegment( final int segment ) { if ( segment <= 0 ) { return getStartOfLine(); } return getPageBreak( segment - 1 ); } /** * Returns the index of the previous pagebreak (the page-boundary that is left to the given position) for the * specified position. This specifies the page-segment in which the position sits. * * @param position * the position in micro-points. * @return the number of the page segment. */ private int findStartOfPageSegmentForPosition( final long position ) { final long[] breaks = getPageBreaks(); final int elementSize = getPagebreakCount(); final int i = CenterAlignmentProcessor.binarySearch( breaks, position, elementSize ); if ( i > -1 ) { return i; } if ( i == -1 ) { return 0; } return Math.min( -( i + 2 ), elementSize - 1 ); } /** * Finds the element that is closest to the given position without being larger than the position. This method returns * endIndex - 1 if all elements are smaller than the given position. * * @param position * the position for which an element should be found. * @param endIndex * the maximum index on which to search in the element-positions list. * @return the index of the element closes to the given position. */ private int findElementLeftOfPosition( final long position, final int endIndex ) { final long[] elementPositions = getElementPositions(); final int i = CenterAlignmentProcessor.binarySearch( elementPositions, position, endIndex ); if ( i > -1 ) { return i; } if ( i == -1 ) { return 0; } // if greater than last break, return the last break .. return Math.min( -( i + 2 ), endIndex - 1 ); } private static int binarySearch( final long[] array, final long key, final int end ) { int low = 0; int high = end - 1; while ( low <= high ) { final int mid = ( low + high ) >>> 1; final long midVal = array[mid]; if ( midVal < key ) { low = mid + 1; } else if ( midVal > key ) { high = mid - 1; } else { return mid; // key found } } return -( low + 1 ); // key not found. } }