/*
* 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.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.engine.classic.core.ElementAlignment;
import org.pentaho.reporting.engine.classic.core.layout.model.InlineRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.LayoutNodeTypes;
import org.pentaho.reporting.engine.classic.core.layout.model.PageGrid;
import org.pentaho.reporting.engine.classic.core.layout.model.ParagraphPoolBox;
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.RenderNode;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderableComplexText;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderableReplacedContentBox;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderableText;
import org.pentaho.reporting.engine.classic.core.layout.model.SpacerRenderNode;
import org.pentaho.reporting.engine.classic.core.layout.model.context.BoxDefinition;
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.alignment.CenterAlignmentProcessor;
import org.pentaho.reporting.engine.classic.core.layout.process.alignment.JustifyAlignmentProcessor;
import org.pentaho.reporting.engine.classic.core.layout.process.alignment.LastLineTextAlignmentProcessor;
import org.pentaho.reporting.engine.classic.core.layout.process.alignment.LeftAlignmentProcessor;
import org.pentaho.reporting.engine.classic.core.layout.process.alignment.RightAlignmentProcessor;
import org.pentaho.reporting.engine.classic.core.layout.process.layoutrules.DefaultSequenceList;
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.InlineNodeSequenceElement;
import org.pentaho.reporting.engine.classic.core.layout.process.layoutrules.ReplacedContentSequenceElement;
import org.pentaho.reporting.engine.classic.core.layout.process.layoutrules.SequenceList;
import org.pentaho.reporting.engine.classic.core.layout.process.layoutrules.SpacerSequenceElement;
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.layout.process.util.CacheBoxShifter;
import org.pentaho.reporting.engine.classic.core.layout.process.valign.AlignContext;
import org.pentaho.reporting.engine.classic.core.layout.process.valign.BoxAlignContext;
import org.pentaho.reporting.engine.classic.core.layout.process.valign.InlineBlockAlignContext;
import org.pentaho.reporting.engine.classic.core.layout.process.valign.NodeAlignContext;
import org.pentaho.reporting.engine.classic.core.layout.process.valign.ReplacedContentAlignContext;
import org.pentaho.reporting.engine.classic.core.layout.process.valign.TextElementAlignContext;
import org.pentaho.reporting.engine.classic.core.layout.process.valign.VerticalAlignmentProcessor;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictGeomUtility;
import org.pentaho.reporting.libraries.base.util.FastStack;
import org.pentaho.reporting.libraries.fonts.registry.FontMetrics;
/**
* This final processing step revalidates the text-layouting and the vertical alignment of block-level elements.
* <p/>
* At this point, the layout is almost finished, but non-dynamic text elements may contain more content on the last line
* than actually needed. This step recomputes the vertical alignment and merges all extra lines into the last line.
*
* @author Thomas Morgner
*/
public final class RevalidateAllAxisLayoutStep { // extends IterateSimpleStructureProcessStep
private static class MergeContext {
private RenderBox readContext;
private RenderBox writeContext;
protected MergeContext( final RenderBox writeContext, final RenderBox readContext ) {
this.readContext = readContext;
this.writeContext = writeContext;
}
public RenderBox getReadContext() {
return readContext;
}
public RenderBox getWriteContext() {
return writeContext;
}
}
private static final Log logger = LogFactory.getLog( RevalidateAllAxisLayoutStep.class );
private static final long OVERFLOW_DUMMY_WIDTH = StrictGeomUtility.toInternalValue( 20000 );
private LastLineTextAlignmentProcessor centerProcessor;
private LastLineTextAlignmentProcessor leftProcessor;
private LastLineTextAlignmentProcessor rightProcessor;
private LastLineTextAlignmentProcessor justifiedProcessor;
private PageGrid pageGrid;
private OutputProcessorMetaData metaData;
private VerticalAlignmentProcessor verticalAlignmentProcessor;
private boolean complexText;
private boolean strictTextProcessing;
public RevalidateAllAxisLayoutStep() {
this.verticalAlignmentProcessor = new VerticalAlignmentProcessor();
}
public void initialize( final OutputProcessorMetaData metaData ) {
this.metaData = metaData;
complexText = metaData.isFeatureSupported( OutputProcessorFeature.COMPLEX_TEXT );
strictTextProcessing = metaData.isFeatureSupported( OutputProcessorFeature.STRICT_TEXT_PROCESSING );
}
public void processBoxChilds( final ParagraphRenderBox box, final PageGrid pageGrid ) {
try {
this.pageGrid = pageGrid;
if ( complexText ) {
processComplexText( box );
} else {
processSimpleText( box );
}
} finally {
this.pageGrid = null;
}
}
private void performVerticalBlockAlignment( final RenderBox box ) {
final RenderNode lastChildNode = box.getLastChild();
if ( lastChildNode == null ) {
return;
}
final BoxDefinition boxDefinition = box.getBoxDefinition();
final StaticBoxLayoutProperties blp = box.getStaticBoxLayoutProperties();
final long insetBottom = blp.getBorderBottom() + boxDefinition.getPaddingBottom();
final long insetTop = blp.getBorderTop() + boxDefinition.getPaddingTop();
final long childY2 =
lastChildNode.getCachedY() + lastChildNode.getCachedHeight() + lastChildNode.getEffectiveMarginBottom();
final long childY1 = box.getFirstChild().getCachedY();
final long usedHeight = ( childY2 - childY1 );
final long computedHeight = box.getCachedHeight();
if ( computedHeight > usedHeight ) {
// we have extra space to distribute. So lets shift some boxes.
final ElementAlignment valign = box.getNodeLayoutProperties().getVerticalAlignment();
if ( ElementAlignment.BOTTOM.equals( valign ) ) {
final long boxBottom = ( box.getCachedY() + box.getCachedHeight() - insetBottom );
final long delta = boxBottom - childY2;
CacheBoxShifter.shiftBoxChilds( box, delta );
} else if ( ElementAlignment.MIDDLE.equals( valign ) ) {
final long extraHeight = computedHeight - usedHeight;
final long boxTop = box.getCachedY() + insetTop + ( extraHeight / 2 );
final long delta = boxTop - childY1;
CacheBoxShifter.shiftBoxChilds( box, delta );
}
}
}
protected void processComplexText( final ParagraphRenderBox paragraph ) {
if ( paragraph.getStaticBoxLayoutProperties().isOverflowY() == true ) {
performVerticalBlockAlignment( paragraph );
return;
}
final RenderNode lastLine = paragraph.getLastChild();
if ( lastLine == null ) {
// Empty paragraph, no need to do anything ...
return;
}
// Process the direct childs of the paragraph
// Each direct child represents a line ..
final long paragraphBottom = paragraph.getCachedY() + paragraph.getCachedHeight();
if ( ( lastLine.getCachedY() + lastLine.getCachedHeight() ) <= paragraphBottom ) {
// Already perfectly aligned.
return;
}
RenderNode node = paragraph.getFirstChild();
ParagraphPoolBox prev = null;
while ( node != null ) {
// all childs of the linebox container must be inline boxes. They
// represent the lines in the paragraph. Any other element here is
// a error that must be reported
if ( node.getNodeType() != LayoutNodeTypes.TYPE_BOX_LINEBOX ) {
throw new IllegalStateException( "Encountered " + node.getClass() );
}
// Process the current line.
final long y = node.getCachedY();
final long height = node.getCachedHeight();
if ( y + height <= paragraphBottom ) {
// Node will fit, so we can allow it ..
prev = (ParagraphPoolBox) node;
node = node.getNext();
continue;
}
// Encountered a line that will not fully fit into the paragraph.
// Merge it with the previous line-paragraph.
if ( prev == null ) {
// none of the lines fits fully, so get the first one at least
rebuildLastLineComplex( (ParagraphPoolBox) node, (RenderBox) node.getNext() );
node = node.getNext();
} else {
rebuildLastLineComplex( prev, (ParagraphPoolBox) node );
}
// now remove all pending lineboxes (they should be empty anyway).
while ( node != null ) {
final RenderNode oldNode = node;
node = node.getNext();
paragraph.remove( oldNode );
}
return;
}
}
protected void processSimpleText( final ParagraphRenderBox paragraph ) {
if ( paragraph.getStaticBoxLayoutProperties().isOverflowY() == true ) {
performVerticalBlockAlignment( paragraph );
return;
}
final RenderNode lastLine = paragraph.getLastChild();
if ( lastLine == null ) {
// Empty paragraph, no need to do anything ...
return;
}
// Process the direct childs of the paragraph
// Each direct child represents a line ..
final long paragraphBottom = paragraph.getCachedY() + paragraph.getCachedHeight();
if ( ( lastLine.getCachedY() + lastLine.getCachedHeight() ) <= paragraphBottom ) {
// Already perfectly aligned.
return;
}
final boolean overflowX = paragraph.getStaticBoxLayoutProperties().isOverflowX();
RenderNode node = paragraph.getFirstChild();
ParagraphPoolBox prev = null;
boolean first =
metaData
.isFeatureSupported( OutputProcessorFeature.BooleanOutputProcessorFeature.ALWAYS_PRINT_FIRST_LINE_OF_TEXT );
while ( node != null ) {
// all childs of the linebox container must be inline boxes. They
// represent the lines in the paragraph. Any other element here is
// a error that must be reported
if ( node.getNodeType() != LayoutNodeTypes.TYPE_BOX_LINEBOX ) {
throw new IllegalStateException( "Encountered " + node.getClass() );
}
final ParagraphPoolBox inlineRenderBox = (ParagraphPoolBox) node;
// Process the current line.
final long y = inlineRenderBox.getCachedY();
final long height = inlineRenderBox.getCachedHeight();
if ( first || y + height <= paragraphBottom ) {
// Node will fit, so we can allow it ..
prev = inlineRenderBox;
node = node.getNext();
first = false;
continue;
}
// Encountered a line that will not fully fit into the paragraph.
// Merge it with the previous line-paragraph.
final ParagraphPoolBox mergedLine = rebuildLastLine( prev, inlineRenderBox );
// now remove all pending lineboxes (they should be empty anyway).
while ( node != null ) {
final RenderNode oldNode = node;
node = node.getNext();
paragraph.remove( oldNode );
}
if ( mergedLine == null ) {
return;
}
final ElementAlignment textAlignment = paragraph.getLastLineAlignment();
final LastLineTextAlignmentProcessor proc = create( textAlignment );
// Now Build the sequence list that holds all nodes for the horizontal alignment computation.
// The last line will get a special "last-line" horizontal alignment. This is quite usefull if
// we are working with justified text and want the last line to be left-aligned.
final SequenceList sequenceList = createHorizontalSequenceList( mergedLine );
final long lineStart = paragraph.getContentAreaX1();
final long lineEnd;
if ( overflowX ) {
lineEnd = OVERFLOW_DUMMY_WIDTH;
} else {
lineEnd = paragraph.getContentAreaX2();
}
if ( lineEnd - lineStart <= 0 ) {
final long minimumChunkWidth = paragraph.getMinimumChunkWidth();
proc.initialize( metaData, sequenceList, lineStart, lineStart + minimumChunkWidth, pageGrid, overflowX );
logger.warn( "Auto-Corrected zero-width linebox." ); // NON-NLS
} else {
proc.initialize( metaData, sequenceList, lineStart, lineEnd, pageGrid, overflowX );
}
proc.performLastLineAlignment();
proc.deinitialize();
// Now Perform the vertical layout for the last line of the paragraph.
final BoxAlignContext valignContext = createVerticalAlignContext( mergedLine );
final StaticBoxLayoutProperties blp = mergedLine.getStaticBoxLayoutProperties();
final BoxDefinition bdef = mergedLine.getBoxDefinition();
final long insetTop = ( blp.getBorderTop() + bdef.getPaddingTop() );
final long contentAreaY1 = mergedLine.getCachedY() + insetTop;
final long lineHeight = mergedLine.getLineHeight();
verticalAlignmentProcessor.align( valignContext, contentAreaY1, lineHeight );
// And finally make sure that the paragraph box itself obeys to the defined vertical box alignment.
performVerticalBlockAlignment( paragraph );
return;
}
}
private BoxAlignContext createVerticalAlignContext( final InlineRenderBox box ) {
BoxAlignContext alignContext = new BoxAlignContext( box );
final FastStack<RenderBox> contextStack = new FastStack<RenderBox>( 50 );
final FastStack<AlignContext> alignContextStack = new FastStack<AlignContext>( 50 );
RenderNode next = box.getFirstChild();
RenderBox context = box;
while ( next != null ) {
// process next
final int nodeType = next.getLayoutNodeType();
if ( ( nodeType & LayoutNodeTypes.MASK_BOX_INLINE ) == LayoutNodeTypes.MASK_BOX_INLINE ) {
final RenderBox nBox = (RenderBox) next;
final RenderNode firstChild = nBox.getFirstChild();
if ( firstChild != null ) {
// Open a non-empty box context
contextStack.push( context );
alignContextStack.push( alignContext );
next = firstChild;
final BoxAlignContext childBoxContext = new BoxAlignContext( nBox );
alignContext.addChild( childBoxContext );
context = nBox;
alignContext = childBoxContext;
} else {
// Process an empty box.
final BoxAlignContext childBoxContext = new BoxAlignContext( nBox );
alignContext.addChild( childBoxContext );
next = nBox.getNext();
}
} else {
// Process an ordinary node.
if ( nodeType == LayoutNodeTypes.TYPE_NODE_TEXT ) {
alignContext.addChild( new TextElementAlignContext( (RenderableText) next ) );
} else if ( nodeType == LayoutNodeTypes.TYPE_BOX_CONTENT ) {
alignContext.addChild( new ReplacedContentAlignContext( (RenderableReplacedContentBox) next, 0 ) );
} else if ( ( nodeType & LayoutNodeTypes.MASK_BOX_BLOCK ) == LayoutNodeTypes.MASK_BOX_BLOCK ) {
alignContext.addChild( new InlineBlockAlignContext( (RenderBox) next ) );
} else {
alignContext.addChild( new NodeAlignContext( next ) );
}
next = next.getNext();
}
while ( next == null && contextStack.isEmpty() == false ) {
// Finish the current box context, if needed
next = context.getNext();
context = contextStack.pop();
alignContext.validate();
alignContext = (BoxAlignContext) alignContextStack.pop();
}
}
return alignContext;
}
private SequenceList createHorizontalSequenceList( final InlineRenderBox box ) {
final SequenceList sequenceList = new DefaultSequenceList();
sequenceList.add( StartSequenceElement.INSTANCE, box );
RenderNode next = box.getFirstChild();
RenderBox context = box;
final FastStack<RenderBox> contextStack = new FastStack<RenderBox>( 50 );
boolean containsContent = false;
while ( next != null ) {
// process next
final int nodeType = next.getLayoutNodeType();
if ( ( nodeType & LayoutNodeTypes.MASK_BOX_INLINE ) == LayoutNodeTypes.MASK_BOX_INLINE ) {
final RenderBox nBox = (RenderBox) next;
final RenderNode firstChild = nBox.getFirstChild();
if ( firstChild != null ) {
// Open a non-empty box context
contextStack.push( context );
next = firstChild;
sequenceList.add( StartSequenceElement.INSTANCE, nBox );
context = nBox;
} else {
// Process an empty box.
sequenceList.add( StartSequenceElement.INSTANCE, nBox );
sequenceList.add( EndSequenceElement.INSTANCE, nBox );
next = nBox.getNext();
}
} else {
// Process an ordinary node.
if ( nodeType == LayoutNodeTypes.TYPE_NODE_TEXT ) {
sequenceList.add( TextSequenceElement.INSTANCE, next );
containsContent = true;
} else if ( nodeType == LayoutNodeTypes.TYPE_BOX_CONTENT ) {
sequenceList.add( ReplacedContentSequenceElement.INSTANCE, next );
containsContent = true;
} else if ( nodeType == LayoutNodeTypes.TYPE_NODE_SPACER ) {
if ( containsContent ) {
sequenceList.add( SpacerSequenceElement.INSTANCE, next );
}
} else if ( ( nodeType & LayoutNodeTypes.MASK_BOX_BLOCK ) == LayoutNodeTypes.MASK_BOX_BLOCK ) {
containsContent = true;
sequenceList.add( InlineBoxSequenceElement.INSTANCE, next );
} else {
containsContent = true;
sequenceList.add( InlineNodeSequenceElement.INSTANCE, next );
}
next = next.getNext();
}
while ( next == null && contextStack.isEmpty() == false ) {
// Finish the current box context, if needed
sequenceList.add( EndSequenceElement.INSTANCE, context );
next = context.getNext();
context = contextStack.pop();
}
}
sequenceList.add( EndSequenceElement.INSTANCE, box );
return sequenceList;
}
private LastLineTextAlignmentProcessor create( final ElementAlignment alignment ) {
if ( ElementAlignment.CENTER.equals( alignment ) ) {
if ( centerProcessor == null ) {
centerProcessor = new CenterAlignmentProcessor();
}
return centerProcessor;
} else if ( ElementAlignment.RIGHT.equals( alignment ) ) {
if ( rightProcessor == null ) {
rightProcessor = new RightAlignmentProcessor();
}
return rightProcessor;
} else if ( ElementAlignment.JUSTIFY.equals( alignment ) ) {
if ( justifiedProcessor == null ) {
justifiedProcessor = new JustifyAlignmentProcessor();
}
return justifiedProcessor;
}
if ( leftProcessor == null ) {
leftProcessor = new LeftAlignmentProcessor();
}
return leftProcessor;
}
private ParagraphPoolBox rebuildLastLine( final ParagraphPoolBox lineBox, final ParagraphPoolBox nextBox ) {
if ( lineBox == null ) {
if ( nextBox == null ) {
throw new NullPointerException( "Both Line- and Next-Line are null." );
}
return rebuildLastLine( nextBox, (ParagraphPoolBox) nextBox.getNext() );
}
if ( nextBox == null || strictTextProcessing ) {
// Linebox is finished, no need to do any merging anymore..
return lineBox;
}
boolean needToAddSpacing = true;
// do the merging ..
final FastStack<MergeContext> contextStack = new FastStack<MergeContext>( 50 );
RenderNode next = nextBox.getFirstChild();
MergeContext context = new MergeContext( lineBox, nextBox );
while ( next != null ) {
// process next
final RenderBox writeContext = context.getWriteContext();
final StaticBoxLayoutProperties staticBoxLayoutProperties = writeContext.getStaticBoxLayoutProperties();
long spaceWidth = staticBoxLayoutProperties.getSpaceWidth();
if ( spaceWidth == 0 ) {
// Space has not been computed yet.
final FontMetrics fontMetrics = metaData.getFontMetrics( writeContext.getStyleSheet() );
spaceWidth = StrictGeomUtility.fromFontMetricsValue( fontMetrics.getCharWidth( ' ' ) );
staticBoxLayoutProperties.setSpaceWidth( spaceWidth );
}
if ( next.isRenderBox() ) {
final RenderBox nBox = (RenderBox) next;
final RenderNode firstChild = nBox.getFirstChild();
if ( firstChild != null ) {
contextStack.push( context );
next = firstChild;
final RenderNode writeContextLastChild = writeContext.getLastChild();
if ( writeContextLastChild.isRenderBox() ) {
if ( writeContextLastChild.getInstanceId() == nBox.getInstanceId() ) {
context = new MergeContext( (RenderBox) writeContextLastChild, nBox );
} else {
if ( needToAddSpacing ) {
if ( spaceWidth > 0 ) {
// docmark: Used zero as new height
final SpacerRenderNode spacer = new SpacerRenderNode( spaceWidth, 0, false, 1 );
spacer.setVirtualNode( true );
writeContext.addGeneratedChild( spacer );
}
needToAddSpacing = false;
}
final RenderBox newWriter = (RenderBox) nBox.derive( false );
newWriter.setVirtualNode( true );
writeContext.addGeneratedChild( newWriter );
context = new MergeContext( newWriter, nBox );
}
} else {
if ( needToAddSpacing ) {
if ( spaceWidth > 0 ) {
// docmark: Used zero as new height
final SpacerRenderNode spacer = new SpacerRenderNode( spaceWidth, 0, false, 1 );
spacer.setVirtualNode( true );
writeContext.addGeneratedChild( spacer );
}
needToAddSpacing = false;
}
final RenderBox newWriter = (RenderBox) nBox.derive( false );
newWriter.setVirtualNode( true );
writeContext.addGeneratedChild( newWriter );
context = new MergeContext( newWriter, nBox );
}
} else {
if ( needToAddSpacing ) {
if ( spaceWidth > 0 ) {
// docmark: Used zero as new height
final SpacerRenderNode spacer = new SpacerRenderNode( spaceWidth, 0, false, 1 );
spacer.setVirtualNode( true );
writeContext.addGeneratedChild( spacer );
}
needToAddSpacing = false;
}
final RenderNode box = nBox.derive( true );
box.setVirtualNode( true );
writeContext.addGeneratedChild( box );
next = nBox.getNext();
}
} else {
if ( needToAddSpacing ) {
final RenderNode lastChild = writeContext.getLastChild();
if ( spaceWidth > 0 && lastChild != null && ( lastChild.getNodeType() != LayoutNodeTypes.TYPE_NODE_SPACER ) ) {
// docmark: Used zero as new height
final SpacerRenderNode spacer = new SpacerRenderNode( spaceWidth, 0, false, 1 );
spacer.setVirtualNode( true );
writeContext.addGeneratedChild( spacer );
}
needToAddSpacing = false;
}
final RenderNode child = next.derive( true );
child.setVirtualNode( true );
writeContext.addGeneratedChild( child );
next = next.getNext();
}
while ( next == null && contextStack.isEmpty() == false ) {
// Log.debug ("FINISH " + context.getReadContext());
next = context.getReadContext().getNext();
context = contextStack.pop();
}
}
return rebuildLastLine( lineBox, (ParagraphPoolBox) nextBox.getNext() );
}
private RenderBox rebuildLastLineComplex( final RenderBox lineBox, final RenderBox nextBox ) {
if ( lineBox == null ) {
throw new NullPointerException();
}
if ( nextBox == null ) {
return lineBox;
}
RenderNode child = nextBox.getFirstChild();
while ( child != null ) {
if ( child.isRenderBox() ) {
if ( lineBox.getLastChild().isRenderBox() && lineBox.getLastChild().getInstanceId() == child.getInstanceId() ) {
rebuildLastLineComplex( (RenderBox) lineBox.getLastChild(), (RenderBox) child );
} else {
RenderBox lineBoxChild = (RenderBox) child.derive( false );
rebuildLastLineComplex( lineBoxChild, (RenderBox) child );
lineBoxChild.close();
lineBox.addGeneratedChild( lineBoxChild );
}
} else if ( child instanceof RenderableComplexText ) {
RenderableComplexText childAsText = (RenderableComplexText) child;
RenderNode n = lineBox.getLastChild();
if ( n instanceof RenderableComplexText ) {
RenderableComplexText lastLine = (RenderableComplexText) n;
if ( lastLine.isSameSource( childAsText ) ) {
lineBox.replaceChild( n, lastLine.merge( childAsText ) );
} else {
lineBox.addGeneratedChild( child );
}
} else {
lineBox.addGeneratedChild( child );
}
} else {
lineBox.addGeneratedChild( child );
}
child = child.getNext();
}
return null;
}
}