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

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

/*
* 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.pentaho.reporting.engine.classic.core.layout.model.BlockRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.CanvasRenderBox;
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.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.RenderableText;
import org.pentaho.reporting.engine.classic.core.layout.process.linebreak.EmptyLinebreaker;
import org.pentaho.reporting.engine.classic.core.layout.process.linebreak.FullLinebreaker;
import org.pentaho.reporting.engine.classic.core.layout.process.linebreak.ParagraphLinebreaker;
import org.pentaho.reporting.engine.classic.core.layout.process.linebreak.SimpleLinebreaker;
import org.pentaho.reporting.libraries.base.util.FastStack;

/**
* This static computation step performs manual linebreaks on all paragraphs. This transforms the pool-collection into
* the lines-collection.
* <p/>
* For now, we follow a very simple path: A paragraph cannot be validated, if it is not yet closed. The linebreaking, be
* it the static one here or the dynamic one later, must be redone when the paragraph changes.
* <p/>
* Splitting for linebreaks happens only between inline-boxes. BlockBoxes that are contained in inline-boxes (like
* 'inline-block' elements or 'inline-tables') are considered unbreakable according to the CSS specs. Linebreaking can
* be suspended in these cases.
* <p/>
* As paragraphs itself are block elements, the linebreaks can be done iterative, using a simple stack to store the
* context of possibly nested paragraphs. The paragraph's pool contains the elements that should be processed, and the
* line-container will receive the pool's content (contained in an artificial inline element, as the linecontainer is a
* block-level element).
* <p/>
* Change-tracking should take place on the paragraph's pool element instead of the paragraph itself. This way, only
* structural changes are taken into account.
*
* @author Thomas Morgner
*/
public final class ParagraphLineBreakStep extends IterateStructuralProcessStep
{
  private static final EmptyLinebreaker LEAF_BREAK_STATE = new EmptyLinebreaker();

  private FastStack paragraphNesting;
  private ParagraphLinebreaker breakState;
  private SimpleLinebreaker reusableSimpleLinebreaker;

  public ParagraphLineBreakStep()
  {
    paragraphNesting = new FastStack(50);
  }

  public void compute(final LogicalPageBox root)
  {
    paragraphNesting.clear();
    try
    {
      startProcessing(root);
    }
    finally
    {
      paragraphNesting.clear();
      breakState = null;
    }
  }

  protected boolean startBlockBox(final BlockRenderBox box)
  {
    if (box.getNodeType() == LayoutNodeTypes.TYPE_BOX_PARAGRAPH)
    {
      final ParagraphRenderBox paragraphBox = (ParagraphRenderBox) box;
      final long poolChangeTracker = paragraphBox.getPool().getChangeTracker();
      final boolean unchanged = poolChangeTracker == paragraphBox.getLineBoxAge();

      if (unchanged)
      {
        // If the paragraph is unchanged (no new elements have been added to the pool) then we can take a
        // shortcut. The childs of this paragraph will also be unchanged (as any structural change would have increased
        // the change-tracker).
        paragraphNesting.push(ParagraphLineBreakStep.LEAF_BREAK_STATE);
        breakState = ParagraphLineBreakStep.LEAF_BREAK_STATE;
        return false;
      }

      // When the paragraph has changed, this can only be caused by someone adding a new node to the paragraph
      // or to one of the childs.

      // Paragraphs can be nested whenever a Inline-Level element declares to be a Block-Layouter. (This is an
      // Inline-Block or Inline-Table case in CSS)

      // It is guaranteed, that if a child is changed, the parent is marked as changed as well.
      // So we have only two cases to deal with: (1) The child is unchanged (2) the child is changed.

      if (breakState == null)
      {
        final ParagraphPoolBox paragraphPoolBox = paragraphBox.getPool();
        final RenderNode firstChild = paragraphPoolBox.getFirstChild();
        if (firstChild == null)
        {
          paragraphBox.setPoolSize(0);
          paragraphBox.setLineBoxAge(paragraphPoolBox.getChangeTracker());
          breakState = ParagraphLineBreakStep.LEAF_BREAK_STATE;
          return false;
        }
        if (firstChild == paragraphPoolBox.getLastChild())
        {
          if ((firstChild.getNodeType() & LayoutNodeTypes.MASK_BOX) != LayoutNodeTypes.MASK_BOX)
          {
            // Optimize away: A single text-element or other content in a linebox. No need to dive deeper.
            paragraphBox.setPoolSize(1);
            paragraphBox.setLineBoxAge(paragraphPoolBox.getChangeTracker());
            breakState = ParagraphLineBreakStep.LEAF_BREAK_STATE;
            return false;
          }
        }
        if (paragraphBox.isComplexParagraph())
        {
          final ParagraphLinebreaker item = new FullLinebreaker(paragraphBox);
          paragraphNesting.push(item);
          breakState = item;
        }
        else
        {
          if (reusableSimpleLinebreaker == null)
          {
            reusableSimpleLinebreaker = new SimpleLinebreaker(paragraphBox);
          }
          else
          {
            reusableSimpleLinebreaker.recycle(paragraphBox);
          }
          paragraphNesting.push(reusableSimpleLinebreaker);
          breakState = reusableSimpleLinebreaker;
        }
        return true;
      }

      // The breakState indicates that there is a paragraph processing active at the moment. This means, the
      // paragraph-box we are dealing with right now is a nested box.

      if (breakState.isWritable() == false)
      {
        // OK, should not happen, but you never know. I'm good at hiding
        // bugs in the code ..
        throw new IllegalStateException("A child cannot be dirty, if the parent is clean");
      }

      // The paragraph is somehow nested in an other paragraph.
      // This cannot be handled by the simple implementation, as we will most likely start to deriveForAdvance childs sooner
      // or later
      if (breakState instanceof FullLinebreaker == false)
      {
        // convert it ..
        final FullLinebreaker fullBreaker = breakState.startComplexLayout();
        paragraphNesting.pop();
        paragraphNesting.push(fullBreaker);
        breakState = fullBreaker;
      }

      final ParagraphLinebreaker subFlow = breakState.startParagraphBox(paragraphBox);
      paragraphNesting.push(subFlow);
      breakState = subFlow;
      return true;
    }

    // some other block box ..
    if (breakState == null)
    {
      if (box.getChangeTracker() == box.getCachedAge())
      {
        return false;
      }
      // Not nested in a paragraph, thats easy ..
      return true;
    }

    if (breakState.isWritable() == false)
    {
      throw new IllegalStateException("This cannot be: There is an active break-state, but the box is not writable.");
    }

    breakState.startBlockBox(box);
    return true;
  }

  public boolean startCanvasBox(final CanvasRenderBox box)
  {
    if (breakState == null)
    {
      if (box.getChangeTracker() == box.getCachedAge())
      {
        return false;
      }

      return true;
    }

    // some other block box .. suspend.
    if (breakState.isWritable() == false)
    {
      throw new IllegalStateException("A child cannot be dirty, if the parent is clean");
    }

    breakState.startBlockBox(box);
    return true;
  }

  public void finishCanvasBox(final CanvasRenderBox box)
  {
    if (breakState != null)
    {
      if (breakState.isWritable() == false)
      {
        throw new IllegalStateException("A child cannot be dirty, if the parent is clean");
      }

      breakState.finishBlockBox(box);
    }
  }

  protected void finishBlockBox(final BlockRenderBox box)
  {
    if (box.getNodeType() == LayoutNodeTypes.TYPE_BOX_PARAGRAPH)
    {
      if (breakState == ParagraphLineBreakStep.LEAF_BREAK_STATE && paragraphNesting.isEmpty())
      {
        // this is a non-nested simple paragraph; no need for clean up ..
        breakState = null;
        return;
      }
      // do the linebreak jiggle ...
      // This is the first test case whether it is possible to avoid
      // composition-recursion on such computations. I'd prefer to have
      // an iterator pattern here ...

      // finally update the change tracker ..
      breakState.finish();
      paragraphNesting.pop();
      if (paragraphNesting.isEmpty())
      {
        if (reusableSimpleLinebreaker != null)
        {
          reusableSimpleLinebreaker.dispose();
        }
        breakState = null;
      }
      else
      {
        breakState = (ParagraphLinebreaker) paragraphNesting.peek();
        breakState.finishParagraphBox((ParagraphRenderBox) box);
      }
      return;
    }

    if (breakState == null)
    {
      return;
    }

    if (breakState.isWritable() == false)
    {
      throw new IllegalStateException("A child cannot be dirty, if the parent is clean");
    }

    breakState.finishBlockBox(box);
  }

  protected boolean startInlineBox(final InlineRenderBox box)
  {
    if (breakState == null || breakState.isWritable() == false)
    {
      if (box.getChangeTracker() == box.getCachedAge())
      {
        return false;
      }
      return true;
    }

    breakState.startInlineBox(box);
    return true;
  }

  protected void finishInlineBox(final InlineRenderBox box)
  {
    if (breakState == null || breakState.isWritable() == false)
    {
      return;
    }

    breakState.finishInlineBox(box);
    if (breakState.isBreakRequested() && isEndOfLine(box) == false)
    {
      performBreak();
    }
  }

  public boolean startRowBox(final RenderBox box)
  {
    if (breakState == null)
    {
      if (box.getChangeTracker() == box.getCachedAge())
      {
        return false;
      }

      return true;
    }

    // some other block box .. suspend.
    if (breakState.isWritable() == false)
    {
      throw new IllegalStateException("A child cannot be dirty, if the parent is clean");
    }

    breakState.startBlockBox(box);
    return true;
  }

  public void finishRowBox(final RenderBox box)
  {
    if (breakState != null)
    {
      if (breakState.isWritable() == false)
      {
        throw new IllegalStateException("A child cannot be dirty, if the parent is clean");
      }

      breakState.finishBlockBox(box);
    }
  }

  protected void processOtherNode(final RenderNode node)
  {
    if (breakState == null || breakState.isWritable() == false)
    {
      return;
    }
    if (breakState.isSuspended() ||
        (node.getNodeType() != LayoutNodeTypes.TYPE_NODE_TEXT))
    {
      breakState.addNode(node);
      return;
    }

    final RenderableText text = (RenderableText) node;
    breakState.addNode(text);
    if (text.isForceLinebreak() == false)
    {
      return;
    }

    if (breakState.isBreakRequested())
    {
      performBreak();
    }

    // OK, someone requested a manual linebreak.
    // Fill a stack with the current context ..
    // Check if we are at the end of the line
    if (node.getNext() == null)
    {
      // OK, if we are at the end of the line (for all contexts), so we
      // dont have to perform a break. The text will end anyway ..
      if (isEndOfLine(node))
      {
        return;
      }

      // as soon as we are no longer the last element - break!
      // According to the flow rules, that will happen in one of the next
      // finishInlineBox events ..
      breakState.setBreakRequested(true);
      return;
    }

    performBreak();
  }

  private boolean isEndOfLine(RenderNode node)
  {
    while (node != null)
    {
      if ((node.getNodeType() & LayoutNodeTypes.MASK_BOX) == LayoutNodeTypes.MASK_BOX &&
          (node.getNodeType() & LayoutNodeTypes.MASK_BOX_INLINE) != LayoutNodeTypes.MASK_BOX_INLINE)
      {
        return true;
      }
      if (node.getNext() != null)
      {
        return false;
      }
      node = node.getParent();
    }
    return true;
  }

  private void performBreak()
  {
    if (breakState instanceof FullLinebreaker == false)
    {
      final FullLinebreaker fullBreaker = breakState.startComplexLayout();
      paragraphNesting.pop();
      paragraphNesting.push(fullBreaker);
      breakState = fullBreaker;

      fullBreaker.performBreak();
    }
    else
    {
      final FullLinebreaker fullBreaker = (FullLinebreaker) breakState;
      fullBreaker.performBreak();
    }
  }

  protected boolean startOtherBox(final RenderBox box)
  {
    if (breakState == null)
    {
      return false;
    }

    if (breakState.isWritable() == false)
    {
      return false;
    }

    breakState.startBlockBox(box);
    return true;
  }

  protected void finishOtherBox(final RenderBox box)
  {
    if (breakState != null && breakState.isWritable())
    {
      breakState.finishBlockBox(box);
    }
  }
}
TOP

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

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.