Package org.pentaho.reporting.engine.classic.core.modules.output.table.base

Source Code of org.pentaho.reporting.engine.classic.core.modules.output.table.base.SheetLayout

/*
* 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.modules.output.table.base;

import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;

import org.pentaho.reporting.engine.classic.core.layout.model.BorderEdge;
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.RenderableReplacedContent;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderableReplacedContentBox;
import org.pentaho.reporting.engine.classic.core.layout.model.context.BoxDefinition;
import org.pentaho.reporting.engine.classic.core.layout.process.ProcessUtility;
import org.pentaho.reporting.engine.classic.core.style.ElementStyleKeys;
import org.pentaho.reporting.engine.classic.core.style.StyleSheet;
import org.pentaho.reporting.engine.classic.core.util.ShapeDrawable;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictBounds;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictGeomUtility;
import org.pentaho.reporting.libraries.resourceloader.factory.drawable.DrawableWrapper;

/**
* The sheet layout is used to build the background map and to collect the x- and y-cell-borders.
*/
public final class SheetLayout
{

  private static final SheetLayoutTableCellDefinition MARKER_DEFINITION = new SheetLayoutTableCellDefinition();

  /**
   * An internal flag indicating that the upper or left bounds should be used.
   */
  private static final boolean UPPER_BOUNDS = true;
  private static final boolean LOWER_BOUNDS = false;

  /**
   * A flag, defining whether to use strict layout mode.
   */
  private final boolean strict;

  /**
   * The XBounds, all vertical cell boundaries (as CoordinateMappings).
   */
  private final TableCutList xBounds;

  /**
   * The YBounds, all vertical cell boundaries (as CoordinateMappings).
   */
  private final TableCutList yBounds;

  /**
   * The right border of the grid. This is needed when not being in the strict mode.
   */
  private long xMaxBounds;
  private long yMaxBounds;
  private boolean ellipseAsRectangle;

  /**
   * Creates a new TableGrid-object. If strict mode is enabled, all cell bounds are used to create the table grid,
   * resulting in a more complex layout.
   *
   * @param strict             the strict mode for the layout.
   * @param ellipseAsRectangle a flag that defines whether ellipse-objects are translated into rectangles and therefore
   *                           create backgrounds.
   */
  public SheetLayout(final boolean strict,
                     final boolean ellipseAsRectangle)
  {
    this.ellipseAsRectangle = ellipseAsRectangle;
    this.xBounds = new TableCutList(50, true);
    this.yBounds = new TableCutList(2500, true);
    this.strict = strict;
    this.xMaxBounds = 0;
    this.yMaxBounds = 0;
    this.ensureXMapping(0, Boolean.FALSE);
    this.ensureYMapping(0, Boolean.FALSE);
  }

  private SheetLayoutTableCellDefinition createBackground(final RenderBox box)
  {
    if (box.getBoxDefinition().getBorder().isEmpty() == false)
    {
      return MARKER_DEFINITION;
    }

    final StyleSheet styleSheet = box.getStyleSheet();
    if (styleSheet.getStyleProperty(ElementStyleKeys.BACKGROUND_COLOR) != null)
    {
      return MARKER_DEFINITION;
    }

    if (styleSheet.getStyleProperty(ElementStyleKeys.ANCHOR_NAME) != null)
    {
      return MARKER_DEFINITION;
    }
    return null;
  }

  private SheetLayoutTableCellDefinition computeLegacyBackground(final RenderBox box, final long shift)
  {

    // For legacy reasons: A single ReplacedContent in a canvas means, we may have a old-style border and
    // background definition.
    if (box.getNodeType() != LayoutNodeTypes.TYPE_BOX_CONTENT)
    {
      return null;
    }
    final RenderableReplacedContentBox contentBox = (RenderableReplacedContentBox) box;

    final StyleSheet styleSheet = box.getStyleSheet();
    final RenderableReplacedContent rpc = contentBox.getContent();
    final Object rawContentObject = rpc.getRawObject();
    if (rawContentObject instanceof DrawableWrapper == false)
    {
      return null;
    }
    final DrawableWrapper wrapper = (DrawableWrapper) rawContentObject;
    final Object rawbackend = wrapper.getBackend();
    if (rawbackend instanceof ShapeDrawable == false)
    {
      return null;
    }
    final ShapeDrawable drawable = (ShapeDrawable) rawbackend;
    final Shape rawObject = drawable.getShape();

    final boolean draw = styleSheet.getBooleanStyleProperty(ElementStyleKeys.DRAW_SHAPE);
    if (draw && rawObject instanceof Line2D)
    {
      final int lineType;
      final long coordinate;
      final Line2D line = (Line2D) rawObject;

      final boolean vertical = line.getX1() == line.getX2();
      final boolean horizontal = line.getY1() == line.getY2();
      if (vertical && horizontal)
      {
        return null;
      }
      if (vertical)
      {
        lineType = SheetLayoutTableCellDefinition.LINE_HINT_VERTICAL;
        coordinate = StrictGeomUtility.toInternalValue(line.getX1()) + box.getX();
      }
      else if (horizontal)
      {
        lineType = SheetLayoutTableCellDefinition.LINE_HINT_HORIZONTAL;
        coordinate = StrictGeomUtility.toInternalValue(line.getY1()) + box.getY() + shift;
      }
      else
      {
        return null;
      }

      return new SheetLayoutTableCellDefinition(lineType, coordinate);
    }

    if (rawObject instanceof Rectangle2D ||
        (ellipseAsRectangle && rawObject instanceof Ellipse2D))
    {
      if (draw)
      {
        final BorderEdge edge = ProcessUtility.produceBorderEdge(box.getStyleSheet());
        if (edge != null)
        {
          return MARKER_DEFINITION;
        }
      }
      if (styleSheet.getBooleanStyleProperty(ElementStyleKeys.FILL_SHAPE))
      {
        return MARKER_DEFINITION;
      }

      return null;
    }

    if (rawObject instanceof RoundRectangle2D)
    {
      if (draw)
      {
        // the beast has a border ..
        final BorderEdge edge = ProcessUtility.produceBorderEdge(box.getStyleSheet());
        if (edge != null)
        {
          return MARKER_DEFINITION;
        }
      }
      if (styleSheet.getBooleanStyleProperty(ElementStyleKeys.FILL_SHAPE))
      {
        return MARKER_DEFINITION;
      }

      return null;
    }

    return null;
  }


  /**
   * Adds the bounds of the given TableCellData to the grid. The bounds given must be the same as the bounds of the
   * element, or the layouting might produce surprising results.
   * <p/>
   * This method will do nothing, if the element has a width and height of zero and does not define any anchors.
   *
   * @param element the position that should be added to the grid (might be null).
   * @param shift   the vertical shift which adjusts the visual position of the content.
   * @return true, if the box has not changed and can be safely skipped.
   * @throws NullPointerException if the bounds are null
   */
  public boolean add(final RenderBox element, final long shift)
  {
    final long shiftedY = element.getY() + shift;
    final long elementY;
    if (shiftedY < 0)
    {
      if ((shiftedY + element.getHeight()) < 0)
      {
        // The box will not be visible at all. (Should not happen in a sane environment ..)
        return true;
      }

      elementY = 0;
    }
    else
    {
      elementY = shiftedY;
    }

    final Object state = element.getTableExportState();
    final TableExportRenderBoxState tablestate;
    final SheetLayoutTableCellDefinition background;
    final boolean unmodified;
    if (state != null)
    {
      tablestate = (TableExportRenderBoxState) state;
      if (tablestate.getBackgroundDefinitionAge() == element.getChangeTracker())
      {
        background = tablestate.getBackgroundDefinition();
        unmodified = true;
      }
      else
      {
        background = createBackground(element);
        tablestate.setBackgroundDefinition(background, element.getChangeTracker());
        unmodified = false;
      }
    }
    else
    {
      tablestate = new TableExportRenderBoxState();
      element.setTableExportState(tablestate);
      background = createBackground(element);
      tablestate.setBackgroundDefinition(background, element.getChangeTracker());
      unmodified = false;
    }


    if (addLine(element, background, elementY, shiftedY, unmodified))
    {
      return unmodified;
    }

    final long elementX = element.getX();
    final long elementRightX = (element.getWidth() + elementX);
    final long elementBottomY = element.getHeight() + shiftedY;

    // collect the bounds and add them to the xBounds and yBounds collection
    // if necessary...
    if (unmodified == false)
    {
      ensureXMapping(elementX, Boolean.FALSE);
    }
    ensureYMapping(elementY, Boolean.FALSE);

    // an end cut is auxilary, if it is not a background and the layout is not strict
    final Boolean aux;
    if ((background == null) && (isStrict() == false))
    {
      aux = Boolean.TRUE;
    }
    else
    {
      aux = Boolean.FALSE;
    }
    ensureXMapping(elementRightX, aux);
    ensureYMapping(elementBottomY, aux);

    // update the collected maximums
    if (xMaxBounds < elementRightX)
    {
      xMaxBounds = elementRightX;
    }
    if (yMaxBounds < elementBottomY)
    {
      yMaxBounds = elementBottomY;
    }

    final boolean isContentBox;
    final Boolean contentBoxHint = element.getContentBox();
    if (Boolean.TRUE.equals(contentBoxHint))
    {
      // once a box is marked as content, then there is no need to check further ..
      isContentBox = contentBoxHint.booleanValue();
    }
    else
    {
      if (element.getFirstChild() == null)
      {
        // empty boxes are never content ...
        isContentBox = false;
      }
      else
      {
        if (contentBoxHint != null && element.getContentAge() == element.getChangeTracker())
        {
          isContentBox = contentBoxHint.booleanValue();
        }
        else
        {
          // once the element has a
          isContentBox = ProcessUtility.isContent(element, ellipseAsRectangle, false);
          if (isContentBox)
          {
            element.setContentBox(Boolean.TRUE);
          }
          else
          {
            element.setContentBox(Boolean.FALSE);
          }
          element.setContentAge(element.getChangeTracker());
        }
      }
    }
    if (isContentBox == false)
    {
      return unmodified;
    }

    final BoxDefinition sblp = element.getBoxDefinition();
    if (sblp.getPaddingTop() != 0 || sblp.getPaddingBottom() != 0)
    {
      final long coordinate = elementBottomY - sblp.getPaddingBottom();
      if (coordinate > 0)
      {
        ensureYMapping(coordinate, Boolean.FALSE);
        if (shiftedY >= 0)
        {
          ensureYMapping(elementY + sblp.getPaddingTop(), Boolean.FALSE);
        }
      }
    }

    if (sblp.getPaddingLeft() != 0)
    {
      // check if the element is a page-spanning element. No top-paddings apply in that case..
      ensureXMapping(elementX + sblp.getPaddingLeft(), Boolean.FALSE);
    }
    if (sblp.getPaddingRight() != 0)
    {
      ensureXMapping(elementRightX - sblp.getPaddingRight(), Boolean.FALSE);
    }

    return unmodified;
  }

  public void addRenderableContent(final RenderableReplacedContentBox element, final long shift)
  {
    final long shiftedY = element.getY() + shift;
    final long elementY;
    if (shiftedY < 0)
    {
      if ((shiftedY + element.getHeight()) < 0)
      {
        // The box will not be visible at all. (Should not happen in a sane environment ..)
        return;
      }

      elementY = 0;
    }
    else
    {
      elementY = shiftedY;
    }

    final SheetLayoutTableCellDefinition background = computeLegacyBackground(element, shift);
    if (addLine(element, background, elementY, shiftedY, false))
    {
      return;
    }

    final long elementX = element.getX();
    final long elementRightX = (element.getWidth() + elementX);
    final long elementBottomY = element.getHeight() + shiftedY;

    // collect the bounds and add them to the xBounds and yBounds collection
    // if necessary...
    final BoxDefinition sblp = element.getBoxDefinition();
    ensureXMapping(elementX + sblp.getPaddingLeft(), Boolean.FALSE);
    ensureYMapping(elementY + sblp.getPaddingTop(), Boolean.FALSE);
    ensureXMapping(elementRightX - sblp.getPaddingRight(), Boolean.FALSE);
    ensureYMapping(elementBottomY - sblp.getPaddingBottom(), Boolean.FALSE);
  }

  private boolean addLine(final RenderBox element,
                          final SheetLayoutTableCellDefinition background,
                          final long elementY,
                          final long shiftedY,
                          final boolean unmodified)
  {

    // This method handles several special cases. If the element is a non-area box with borderss,
    // it mapps the borders into a equivalent line-definition.
    final long width = element.getWidth();
    final long height = element.getHeight();
    if (width == 0 && height == 0)
    {
      if (background != null)
      {
        // Elements that define anchors are an exception. We add it ..
        return false;
      }
      // this element will be invisible. We do not add it to the layout; signal that the element has been handled ..
      return true;
    }

    if (width != 0 && height != 0)
    {
      return false;
    }
    if (background == null)
    {
      return false;
    }
    if (background.getLineType() == SheetLayoutTableCellDefinition.LINE_HINT_NONE)
    {
      return false;
    }

    final long elementX = element.getX();
    final long elementRightX = (element.getWidth() + elementX);
    final long elementBottomY = element.getHeight() + shiftedY;

    // Beginn the mapping ..
    if (background.getLineType() == SheetLayoutTableCellDefinition.LINE_HINT_HORIZONTAL)
    {
      if (unmodified == false)
      {
        ensureXMapping(elementX, Boolean.FALSE);
      }
      ensureXMapping(elementRightX, Boolean.FALSE);

      if (background.getCoordinate() == 0)
      {
        ensureYMapping(elementY, Boolean.FALSE);
      }
      else
      {
        ensureYMapping(elementBottomY, Boolean.FALSE);
      }
    }
    else // if (lineVertical)
    {
      ensureYMapping(elementY, Boolean.FALSE);
      ensureYMapping(elementBottomY, Boolean.FALSE);

      if (background.getCoordinate() == 0)
      {
        if (unmodified == false)
        {
          ensureXMapping(elementX, Boolean.FALSE);
        }
      }
      else
      {
        ensureXMapping(elementRightX, Boolean.FALSE);
      }
    }

    // update the collected maximums
    if (xMaxBounds < elementRightX)
    {
      xMaxBounds = elementRightX;
    }
    if (yMaxBounds < elementBottomY)
    {
      yMaxBounds = elementBottomY;
    }

    return true;
  }

  private void ensureXMapping(final long coordinate, final Boolean aux)
  {
    xBounds.put(coordinate, aux);
  }

  private void ensureYMapping(final long coordinate, final Boolean aux)
  {
    yBounds.put(coordinate, aux);
  }

  /**
   * Gets the strict mode flag.
   *
   * @return true, if strict mode is enabled, false otherwise.
   */
  public boolean isStrict()
  {
    return strict;
  }

  public boolean isEmpty()
  {
    return yMaxBounds == 0 && xMaxBounds == 0;
  }

  /**
   * Returns the position of the given element within the table. The TableRectangle contains row and cell indices, no
   * layout coordinates.
   *
   * @param x      the element bounds for which the table bounds should be found.
   * @param y      the element bounds for which the table bounds should be found.
   * @param width  the element bounds for which the table bounds should be found.
   * @param height the element bounds for which the table bounds should be found.
   * @param rect   the returned rectangle or null, if a new instance should be created
   * @return the filled table rectangle.
   */
  public TableRectangle getTableBounds(final long x, final long y, final long width, final long height,
                                       TableRectangle rect)
  {
    if (rect == null)
    {
      rect = new TableRectangle();
    }
    final int x1 = xBounds.findKeyPosition(x, SheetLayout.LOWER_BOUNDS);
    final int y1 = yBounds.findKeyPosition(y, SheetLayout.LOWER_BOUNDS);
    final int x2 = xBounds.findKeyPosition(x + width, SheetLayout.UPPER_BOUNDS);
    final int y2 = yBounds.findKeyPosition(y + height, SheetLayout.UPPER_BOUNDS);
    rect.setRect(x1, y1, x2, y2);
    return rect;
  }

  public TableRectangle getTableBoundsWithCache(final long x, final long y, final long width, final long height,
                                                final TableRectangle rect)
  {
    if (rect == null)
    {
      throw new NullPointerException();
    }
    final int x1 = xBounds.findKeyPosition(x, SheetLayout.LOWER_BOUNDS, rect.getX1());
    final int y1 = yBounds.findKeyPosition(y, SheetLayout.LOWER_BOUNDS, rect.getY1());
    final int x2 = xBounds.findKeyPosition(x + width, SheetLayout.UPPER_BOUNDS, rect.getX2());
    final int y2 = yBounds.findKeyPosition(y + height, SheetLayout.UPPER_BOUNDS, rect.getY2());
    rect.setRect(x1, y1, x2, y2);
    return rect;
  }

  public int getColSpan(final int x1, final long endPosition)
  {
    final int x2 = xBounds.findKeyPosition(endPosition, SheetLayout.UPPER_BOUNDS);
    return x2 - x1;
  }

  public int getRowSpan(final int y1, final long endPosition)
  {
    final int y2 = yBounds.findKeyPosition(endPosition, SheetLayout.UPPER_BOUNDS);
    return y2 - y1;
  }

  /**
   * Returns the position of the given element within the table. The TableRectangle contains row and cell indices, no
   * layout coordinates.
   *
   * @param bounds the element bounds for which the table bounds should be found.
   * @param rect   the returned rectangle or null, if a new instance should be created
   * @return the filled table rectangle.
   */
  public TableRectangle getTableBounds(final StrictBounds bounds,
                                       TableRectangle rect)
  {
    if (bounds == null)
    {
      throw new NullPointerException();
    }
    if (rect == null)
    {
      rect = new TableRectangle();
    }
    final long xCoord = bounds.getX();
    final long yCoord = bounds.getY();
    final int x1 = xBounds.findKeyPosition(xCoord, SheetLayout.LOWER_BOUNDS);
    final int y1 = yBounds.findKeyPosition(yCoord, SheetLayout.LOWER_BOUNDS);
    final int x2 = xBounds.findKeyPosition(xCoord + bounds.getWidth(), SheetLayout.UPPER_BOUNDS);
    final int y2 = yBounds.findKeyPosition(yCoord + bounds.getHeight(), SheetLayout.UPPER_BOUNDS);
    rect.setRect(x1, y1, x2, y2);
    return rect;
  }

  /**
   * A Callback method to inform the sheet layout, that the current page is complete, and no more content will be
   * added.
   */
  public void pageCompleted()
  {
    removeAuxilaryBounds();
  }

  protected void removeAuxilaryBounds()
  {
    ensureXMapping(this.xMaxBounds, Boolean.FALSE);
    ensureYMapping(this.yMaxBounds, Boolean.FALSE);

    // Log.debug("Size: " + getRowCount() + ", " + getColumnCount());

    final long[] removedXCuts = new long[xBounds.size()];
    final Boolean[] xEntries = xBounds.getRawEntries();
    final int xEntrySize = xBounds.size();
    int arrayIdx = 0;
    for (int i = 0; i < xEntrySize; i++)
    {
      final Boolean cut = xEntries[i];
      if (Boolean.TRUE.equals(cut))
      {
        removedXCuts[arrayIdx] = xBounds.getKeyAt(i);
        arrayIdx += 1;
      }
    }

    xBounds.removeAll(removedXCuts, arrayIdx);

    arrayIdx = 0;
    final long[] removedYCuts = new long[yBounds.size()];
    final Boolean[] yEntries = yBounds.getRawEntries();
    final int ySize = yBounds.size();
    for (int i = 0; i < ySize; i++)
    {
      final Boolean cut = yEntries[i];
      if (Boolean.TRUE.equals(cut))
      {
        removedYCuts[arrayIdx] = yBounds.getKeyAt(i);
        arrayIdx += 1;
      }
    }
    yBounds.removeAll(removedYCuts, arrayIdx);
  }

  /**
   * Computes the height of the given row.
   *
   * @param row the row, for which the height should be computed.
   * @return the height of the row.
   * @throws IndexOutOfBoundsException if the row is invalid.
   */
  public long getRowHeight(final int row)
  {
    final int rowCount = yBounds.size();
    if (row >= rowCount)
    {
      throw new IndexOutOfBoundsException
          ("Row " + row + " is invalid. Max valid row is " + (rowCount - 1));
    }

    final long bottomBorder;
    if ((row + 1) < rowCount)
    {
      bottomBorder = yBounds.getKeyAt(row + 1);
    }
    else
    {
      bottomBorder = yMaxBounds;
    }

    return bottomBorder - yBounds.getKeyAt(row);
  }

  public long getMaxHeight()
  {
    return yMaxBounds;
  }

  public long getMaxWidth()
  {
    return xMaxBounds;
  }

  public long getCellWidth(final int startCell)
  {
    return getCellWidth(startCell, startCell + 1);
  }

  /**
   * Computes the height of the given row.
   *
   * @param startCell the first cell in the range
   * @param endCell   the last cell included in the cell range
   * @return the height of the row.
   * @throws IndexOutOfBoundsException if the row is invalid.
   */
  public long getCellWidth(final int startCell, final int endCell)
  {
    if (startCell < 0)
    {
      throw new IndexOutOfBoundsException("Start-Cell must not be negative");
    }
    if (endCell < 0)
    {
      throw new IndexOutOfBoundsException("End-Cell must not be negative");
    }
    if (endCell < startCell)
    {
      throw new IndexOutOfBoundsException("End-Cell must not smaller than end-cell");
    }

    final long rightBorder;
    if (endCell >= xBounds.size())
    {
      rightBorder = xMaxBounds;
    }
    else
    {
      rightBorder = xBounds.getKeyAt(endCell);
    }
    return rightBorder - xBounds.getKeyAt(startCell);
  }

  public long getRowHeight(final int startRow, final int endRow)
  {
    if (startRow < 0)
    {
      throw new IndexOutOfBoundsException("Start-Cell must not be negative");
    }
    if (endRow < 0)
    {
      throw new IndexOutOfBoundsException("End-Cell must not be negative");
    }
    if (endRow < startRow)
    {
      throw new IndexOutOfBoundsException("End-Cell must not smaller than end-cell");
    }

    final long bottomBorder;
    if (endRow >= yBounds.size())
    {
      bottomBorder = yMaxBounds;
    }
    else
    {
      bottomBorder = yBounds.getKeyAt(endRow);
    }
    return bottomBorder - yBounds.getKeyAt(startRow);
  }

  /**
   * The current number of columns. Of course, this value begins to be reliable, once the number of columns is known
   * (that is at the end of the layouting process).
   *
   * @return the number columns.
   */
  public int getColumnCount()
  {
    return Math.max(xBounds.size() - 1, 0);
  }

  /**
   * The current number of rows. Of course, this value begins to be reliable, once the number of rows is known (that is
   * at the end of the layouting process).
   *
   * @return the number columns.
   */
  public int getRowCount()
  {
    return Math.max(yBounds.size() - 1, 0);
  }

  public long getXPosition(final int col)
  {
    return xBounds.getKeyAt(col);
  }

  public long getYPosition(final int row)
  {
    return yBounds.getKeyAt(row);
  }

}
TOP

Related Classes of org.pentaho.reporting.engine.classic.core.modules.output.table.base.SheetLayout

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.