Package org.pentaho.reporting.engine.classic.core.layout.process

Source Code of org.pentaho.reporting.engine.classic.core.layout.process.RevalidateAllAxisLayoutStep

/*
* 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;

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.LogicalPageBox;
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.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.OutputProcessorMetaData;
import org.pentaho.reporting.engine.classic.core.layout.process.alignment.CenterAlignmentProcessor;
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.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.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.style.ElementStyleKeys;
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 IterateVisualProcessStep
{
  private static final Log logger = LogFactory.getLog(RevalidateAllAxisLayoutStep.class);

  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 LastLineTextAlignmentProcessor centerProcessor;
  private LastLineTextAlignmentProcessor leftProcessor;
  private LastLineTextAlignmentProcessor rightProcessor;
  private PageGrid pageGrid;
  private OutputProcessorMetaData metaData;
  private VerticalAlignmentProcessor verticalAlignmentProcessor;

  public RevalidateAllAxisLayoutStep(final OutputProcessorMetaData metaData)
  {
    this.metaData = metaData;
    this.verticalAlignmentProcessor = new VerticalAlignmentProcessor();
  }

  public void compute(final LogicalPageBox pageBox)
  {
    this.pageGrid = pageBox.getPageGrid();
    try
    {
      startProcessing(pageBox);
    }
    finally
    {
      this.pageGrid = null;
    }
  }

  protected boolean startBlockLevelBox(final RenderBox box)
  {
    if (box.isCacheValid())
    {
      return false;
    }
    return true;
  }

  protected boolean startRowLevelBox(final RenderBox box)
  {
    if (box.isCacheValid())
    {
      return false;
    }
    return true;
  }


  protected boolean startCanvasLevelBox(final RenderBox box)
  {
    if (box.isCacheValid())
    {
      return false;
    }
    return true;
  }

  private void performVerticalBlockAlignment(final ParagraphRenderBox 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 processParagraphChilds(final ParagraphRenderBox paragraph)
  {
    if (paragraph.getStaticBoxLayoutProperties().isOverflowY() == true)
    {
      performVerticalBlockAlignment(paragraph);
      return;
    }
   
    final boolean overflowX = paragraph.getStaticBoxLayoutProperties().isOverflowX();

    // Process the direct childs of the paragraph
    // Each direct child represents a line ..
    final long paragraphBottom = paragraph.getCachedY() + paragraph.getCachedHeight();

    final RenderNode lastLine = paragraph.getLastChild();
    if (lastLine == null)
    {
      // Empty paragraph, no need to do anything ...
      return;
    }

    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());
      }

      final ParagraphPoolBox inlineRenderBox = (ParagraphPoolBox) node;
      // Process the current line.
      final long y = inlineRenderBox.getCachedY();
      final long height = inlineRenderBox.getCachedHeight();
      if (y + height <= paragraphBottom)
      {

        // Node will fit, so we can allow it ..
        prev = inlineRenderBox;
        node = node.getNext();
        continue;
      }

      // Encountered a paragraph 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 = Integer.MAX_VALUE;
      }
      else
      {
        lineEnd = paragraph.getContentAreaX2();
      }

      if (lineEnd - lineStart <= 0)
      {
        final long minimumChunkWidth = paragraph.getMinimumChunkWidth();
        proc.initialize(metaData, sequenceList, lineStart, lineStart + minimumChunkWidth, pageGrid, overflowX);
        RevalidateAllAxisLayoutStep.logger.warn("Revalidate: Auto-Corrected zero-width linebox.");
      }
      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 contextStack = new FastStack(50);
    final FastStack alignContextStack = new FastStack(50);
    RenderNode next = box.getFirstChild();
    RenderBox context = box;

    while (next != null)
    {
      // process next
      if ((next.getNodeType() & 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 (next.getNodeType() == LayoutNodeTypes.TYPE_NODE_TEXT)
        {
          alignContext.addChild(new TextElementAlignContext((RenderableText) next));
        }
        else if (next.getNodeType() == LayoutNodeTypes.TYPE_BOX_CONTENT)
        {
          alignContext.addChild(new ReplacedContentAlignContext((RenderableReplacedContentBox) next, 0));
        }
        else if ((next.getNodeType() & 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 = (RenderBox) contextStack.pop();
        alignContext.validate();
        alignContext = (BoxAlignContext) alignContextStack.pop();
      }
    }
    return alignContext;
  }


  private SequenceList createHorizontalSequenceList(final InlineRenderBox box)
  {
    final SequenceList sequenceList = new SequenceList();
    sequenceList.add(StartSequenceElement.INSTANCE, box);

    RenderNode next = box.getFirstChild();
    RenderBox context = box;

    final FastStack contextStack = new FastStack(50);
    boolean containsContent = false;

    while (next != null)
    {
      // process next
      if ((next.getNodeType() & 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 (next.getNodeType() == LayoutNodeTypes.TYPE_NODE_TEXT)
        {
          sequenceList.add(TextSequenceElement.INSTANCE, next);
          containsContent = true;
        }
        else if (next.getNodeType() == LayoutNodeTypes.TYPE_BOX_CONTENT)
        {
          sequenceList.add(ReplacedContentSequenceElement.INSTANCE, next);
          containsContent = true;
        }
        else if (next.getNodeType() == LayoutNodeTypes.TYPE_NODE_SPACER)
        {
          if (containsContent)
          {
            sequenceList.add(SpacerSequenceElement.INSTANCE, next);
          }
        }
        else if ((next.getNodeType() & 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 = (RenderBox) contextStack.pop();
      }
    }

    sequenceList.add(EndSequenceElement.INSTANCE, box);
    return sequenceList;
  }

  /**
   * Reuse the processors ..
   *
   * @param alignment
   * @return
   */
  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;
    }

    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)
    {
      // Linebox is finished, no need to do any merging anymore..
      return lineBox;
    }

    boolean needToAddSpacing = true;

    // do the merging ..
    final FastStack contextStack = new FastStack(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 = fontMetrics.getCharWidth(' ');
        staticBoxLayoutProperties.setSpaceWidth(spaceWidth);
      }

      if ((next.getNodeType() & LayoutNodeTypes.MASK_BOX) == LayoutNodeTypes.MASK_BOX)
      {
        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.getNodeType() & LayoutNodeTypes.MASK_BOX) == LayoutNodeTypes.MASK_BOX)
          {
            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 = (MergeContext) contextStack.pop();
      }
    }

    return rebuildLastLine(lineBox, (ParagraphPoolBox) nextBox.getNext());
  }
}
TOP

Related Classes of org.pentaho.reporting.engine.classic.core.layout.process.RevalidateAllAxisLayoutStep

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.