All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.helger.pdflayout.element.hbox.AbstractPLHBox Maven / Gradle / Ivy

/**
 * Copyright (C) 2014-2016 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.helger.pdflayout.element.hbox;

import java.awt.Color;
import java.io.IOException;

import javax.annotation.CheckForSigned;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.OverridingMethodsMustInvokeSuper;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.ReturnsMutableCopy;
import com.helger.commons.collection.ext.CommonsArrayList;
import com.helger.commons.collection.ext.ICommonsList;
import com.helger.commons.debug.GlobalDebug;
import com.helger.commons.string.ToStringGenerator;
import com.helger.pdflayout.PLDebug;
import com.helger.pdflayout.base.AbstractPLElement;
import com.helger.pdflayout.base.IPLElement;
import com.helger.pdflayout.base.IPLHasVerticalAlignment;
import com.helger.pdflayout.base.IPLRenderableObject;
import com.helger.pdflayout.element.PLRenderHelper;
import com.helger.pdflayout.pdfbox.PDPageContentStreamWithCache;
import com.helger.pdflayout.render.PageSetupContext;
import com.helger.pdflayout.render.PreparationContext;
import com.helger.pdflayout.render.RenderingContext;
import com.helger.pdflayout.spec.BorderSpec;
import com.helger.pdflayout.spec.BorderStyleSpec;
import com.helger.pdflayout.spec.EVertAlignment;
import com.helger.pdflayout.spec.SizeSpec;
import com.helger.pdflayout.spec.WidthSpec;

/**
 * Horizontal box - groups several columns.
 *
 * @author Philip Helger
 * @param 
 *        Implementation type
 */
public abstract class AbstractPLHBox > extends AbstractPLElement 
{
  private static final Logger s_aLogger = LoggerFactory.getLogger (AbstractPLHBox.class);

  protected final ICommonsList  m_aColumns = new CommonsArrayList<> ();
  private int m_nStarWidthItems = 0;
  private BorderSpec m_aColumnBorder = BorderSpec.BORDER0;
  private Color m_aColumnFillColor = null;

  /** prepare width (without padding and margin) */
  protected float [] m_aPreparedColumnWidth;
  /** prepare height (without padding and margin) */
  protected float [] m_aPreparedColumnHeight;

  public AbstractPLHBox ()
  {}

  @Nonnull
  @OverridingMethodsMustInvokeSuper
  public IMPLTYPE setBasicDataFrom (@Nonnull final AbstractPLHBox  aSource)
  {
    super.setBasicDataFrom (aSource);
    setColumnBorder (aSource.m_aColumnBorder);
    setColumnFillColor (aSource.m_aColumnFillColor);
    return thisAsT ();
  }

  /**
   * @return The number of columns. Always ≥ 0.
   */
  @Nonnegative
  public int getColumnCount ()
  {
    return m_aColumns.size ();
  }

  /**
   * @return All columns. Never null.
   */
  @Nonnull
  @ReturnsMutableCopy
  public ICommonsList  getAllColumns ()
  {
    return m_aColumns.getClone ();
  }

  @Nullable
  public PLHBoxColumn getColumnAtIndex (@Nonnegative final int nIndex)
  {
    return m_aColumns.getAtIndex (nIndex);
  }

  @Nullable
  public PLHBoxColumn getFirstColumn ()
  {
    return m_aColumns.getFirst ();
  }

  @Nullable
  public PLHBoxColumn getLastColumn ()
  {
    return m_aColumns.getLast ();
  }

  @Nullable
  public IPLRenderableObject  getColumnElementAtIndex (@Nonnegative final int nIndex)
  {
    final PLHBoxColumn aColumn = getColumnAtIndex (nIndex);
    return aColumn == null ? null : aColumn.getElement ();
  }

  @Nullable
  public IPLRenderableObject  getFirstColumnElement ()
  {
    final PLHBoxColumn aColumn = getFirstColumn ();
    return aColumn == null ? null : aColumn.getElement ();
  }

  @Nullable
  public IPLRenderableObject  getLastColumnElement ()
  {
    final PLHBoxColumn aColumn = getLastColumn ();
    return aColumn == null ? null : aColumn.getElement ();
  }

  @Nonnull
  private PLHBoxColumn _addAndReturnColumn (@CheckForSigned final int nIndex,
                                            @Nonnull final IPLRenderableObject  aElement,
                                            @Nonnull final WidthSpec aWidth)
  {
    internalCheckNotPrepared ();
    final PLHBoxColumn aItem = new PLHBoxColumn (aElement, aWidth);
    if (nIndex < 0 || nIndex >= m_aColumns.size ())
      m_aColumns.add (aItem);
    else
      m_aColumns.add (nIndex, aItem);
    if (aWidth.isStar ())
      m_nStarWidthItems++;
    return aItem;
  }

  @Nonnull
  public PLHBoxColumn addAndReturnColumn (@Nonnull final IPLRenderableObject  aElement,
                                          @Nonnull final WidthSpec aWidth)
  {
    internalCheckNotPrepared ();
    return _addAndReturnColumn (-1, aElement, aWidth);
  }

  @Nonnull
  public IMPLTYPE addColumn (@Nonnull final IPLRenderableObject  aElement, @Nonnull final WidthSpec aWidth)
  {
    addAndReturnColumn (aElement, aWidth);
    return thisAsT ();
  }

  @Nonnull
  public PLHBoxColumn addAndReturnColumn (@Nonnegative final int nIndex,
                                          @Nonnull final IPLRenderableObject  aElement,
                                          @Nonnull final WidthSpec aWidth)
  {
    ValueEnforcer.isGE0 (nIndex, "Index");
    internalCheckNotPrepared ();
    return _addAndReturnColumn (nIndex, aElement, aWidth);
  }

  @Nonnull
  public IMPLTYPE addColumn (@Nonnegative final int nIndex,
                             @Nonnull final IPLRenderableObject  aElement,
                             @Nonnull final WidthSpec aWidth)
  {
    addAndReturnColumn (nIndex, aElement, aWidth);
    return thisAsT ();
  }

  @Nonnull
  public IMPLTYPE removeColumn (@Nonnegative final int nIndex)
  {
    ValueEnforcer.isGE0 (nIndex, "Index");
    internalCheckNotPrepared ();
    final PLHBoxColumn aColumn = m_aColumns.remove (nIndex);
    if (aColumn.getWidth ().isStar ())
      m_nStarWidthItems--;
    return thisAsT ();
  }

  /**
   * Set the border around each contained column.
   *
   * @param aBorder
   *        The border style to use. May be null.
   * @return this
   */
  @Nonnull
  public final IMPLTYPE setColumnBorder (@Nullable final BorderStyleSpec aBorder)
  {
    return setColumnBorder (new BorderSpec (aBorder));
  }

  /**
   * Set the border around each contained column.
   *
   * @param aBorderY
   *        The border to set for top and bottom. Maybe null.
   * @param aBorderX
   *        The border to set for left and right. Maybe null.
   * @return this
   */
  @Nonnull
  public final IMPLTYPE setColumnBorder (@Nullable final BorderStyleSpec aBorderY,
                                         @Nullable final BorderStyleSpec aBorderX)
  {
    return setColumnBorder (new BorderSpec (aBorderY, aBorderX));
  }

  /**
   * Set the border around each contained column.
   *
   * @param aBorderTop
   *        The border to set for top. Maybe null.
   * @param aBorderRight
   *        The border to set for right. Maybe null.
   * @param aBorderBottom
   *        The border to set for bottom. Maybe null.
   * @param aBorderLeft
   *        The border to set for left. Maybe null.
   * @return this
   */
  @Nonnull
  public final IMPLTYPE setColumnBorder (@Nullable final BorderStyleSpec aBorderTop,
                                         @Nullable final BorderStyleSpec aBorderRight,
                                         @Nullable final BorderStyleSpec aBorderBottom,
                                         @Nullable final BorderStyleSpec aBorderLeft)
  {
    return setColumnBorder (new BorderSpec (aBorderTop, aBorderRight, aBorderBottom, aBorderLeft));
  }

  /**
   * Set the border around each contained column.
   *
   * @param aBorder
   *        The border to set. May not be null.
   * @return this
   */
  @Nonnull
  public final IMPLTYPE setColumnBorder (@Nonnull final BorderSpec aBorder)
  {
    ValueEnforcer.notNull (aBorder, "ColumnBorder");
    internalCheckNotPrepared ();
    m_aColumnBorder = aBorder;
    return thisAsT ();
  }

  /**
   * Set the top border value around each contained column. This method may not
   * be called after an element got prepared!
   *
   * @param aBorder
   *        The value to use. May be null.
   * @return this
   */
  @Nonnull
  public final IMPLTYPE setColumnBorderTop (@Nullable final BorderStyleSpec aBorder)
  {
    return setColumnBorder (m_aColumnBorder.getCloneWithTop (aBorder));
  }

  /**
   * Set the right border value around each contained column. This method may
   * not be called after an element got prepared!
   *
   * @param aBorder
   *        The value to use. May be null.
   * @return this
   */
  @Nonnull
  public final IMPLTYPE setColumnBorderRight (@Nullable final BorderStyleSpec aBorder)
  {
    return setColumnBorder (m_aColumnBorder.getCloneWithRight (aBorder));
  }

  /**
   * Set the bottom border value around each contained column. This method may
   * not be called after an element got prepared!
   *
   * @param aBorder
   *        The value to use. May be null.
   * @return this
   */
  @Nonnull
  public final IMPLTYPE setColumnBorderBottom (@Nullable final BorderStyleSpec aBorder)
  {
    return setColumnBorder (m_aColumnBorder.getCloneWithBottom (aBorder));
  }

  /**
   * Set the left border value around each contained column. This method may not
   * be called after an element got prepared!
   *
   * @param aBorder
   *        The value to use. May be null.
   * @return this
   */
  @Nonnull
  public final IMPLTYPE setColumnBorderLeft (@Nullable final BorderStyleSpec aBorder)
  {
    return setColumnBorder (m_aColumnBorder.getCloneWithLeft (aBorder));
  }

  /**
   * Get the border around each contained column. By default
   * {@link BorderSpec#BORDER0} which means no border is used.
   *
   * @return Never null.
   */
  @Nonnull
  public final BorderSpec getColumnBorder ()
  {
    return m_aColumnBorder;
  }

  /**
   * Set the fill color to be used to fill the whole column. null
   * means no fill color.
   *
   * @param aColumnFillColor
   *        The fill color to use. May be null to indicate no fill
   *        color (which is also the default).
   * @return this
   */
  @Nonnull
  public IMPLTYPE setColumnFillColor (@Nullable final Color aColumnFillColor)
  {
    m_aColumnFillColor = aColumnFillColor;
    return thisAsT ();
  }

  /**
   * Get the fill color to be used to fill the whole column. null
   * means no fill color.
   *
   * @return May be null.
   */
  @Nullable
  public Color getColumnFillColor ()
  {
    return m_aColumnFillColor;
  }

  @Override
  @OverridingMethodsMustInvokeSuper
  protected SizeSpec onPrepare (@Nonnull final PreparationContext aCtx) throws IOException
  {
    m_aPreparedColumnWidth = new float [m_aColumns.size ()];
    m_aPreparedColumnHeight = new float [m_aColumns.size ()];
    final float fColumnBorderXSumWidth = m_aColumnBorder.getXSumWidth ();
    final float fColumnBorderYSumWidth = m_aColumnBorder.getYSumWidth ();

    float fUsedWidthFull = fColumnBorderXSumWidth * m_aColumns.size ();
    float fUsedHeightFull = 0;
    final float fAvailableWidth = aCtx.getAvailableWidth () - fUsedWidthFull;
    final float fAvailableHeight = aCtx.getAvailableHeight ();

    int nIndex = 0;
    float fRestWidth = fAvailableWidth;
    // 1. all non-star width items
    for (final PLHBoxColumn aColumn : m_aColumns)
    {
      if (!aColumn.getWidth ().isStar ())
      {
        final IPLRenderableObject  aElement = aColumn.getElement ();
        // Full width of this element
        final float fItemWidthFull = aColumn.getWidth ().getEffectiveValue (fAvailableWidth);
        // Effective content width of this element
        final float fItemWidth = fItemWidthFull - aElement.getFullXSum ();
        // Prepare child element
        final float fItemHeight = aElement.prepare (new PreparationContext (aCtx.getGlobalContext (),
                                                                            fItemWidth,
                                                                            fAvailableHeight))
                                          .getHeight ();
        final float fItemHeightFull = fItemHeight + aElement.getFullYSum ();
        // Update used width and height
        fUsedWidthFull += fItemWidthFull;
        fRestWidth -= fItemWidthFull;
        fUsedHeightFull = Math.max (fUsedHeightFull, fItemHeightFull);
        // Remember width and height for element (without padding and margin)
        m_aPreparedColumnWidth[nIndex] = fItemWidth;
        m_aPreparedColumnHeight[nIndex] = fItemHeight;
      }
      ++nIndex;
    }
    // 2. all star widths items
    nIndex = 0;
    for (final PLHBoxColumn aColumn : m_aColumns)
    {
      if (aColumn.getWidth ().isStar ())
      {
        final IPLRenderableObject  aElement = aColumn.getElement ();
        // Full width of this element
        final float fItemWidthFull = fRestWidth / m_nStarWidthItems;
        // Effective content width of this element
        final float fItemWidth = fItemWidthFull - aElement.getFullXSum ();
        // Prepare child element
        final float fItemHeight = aElement.prepare (new PreparationContext (aCtx.getGlobalContext (),
                                                                            fItemWidth,
                                                                            fAvailableHeight))
                                          .getHeight ();
        final float fItemHeightFull = fItemHeight + aElement.getFullYSum ();
        // Update used width and height
        fUsedWidthFull += fItemWidthFull;
        fUsedHeightFull = Math.max (fUsedHeightFull, fItemHeightFull);
        // Remember width and height for element (without padding and margin)
        m_aPreparedColumnWidth[nIndex] = fItemWidth;
        m_aPreparedColumnHeight[nIndex] = fItemHeight;
      }
      ++nIndex;
    }

    // Apply vertical alignment
    {
      nIndex = 0;
      for (final PLHBoxColumn aColumn : m_aColumns)
      {
        final IPLRenderableObject  aElement = aColumn.getElement ();
        if (aElement instanceof IPLHasVerticalAlignment )
        {
          final EVertAlignment eVertAlignment = ((IPLHasVerticalAlignment ) aElement).getVertAlign ();
          float fPaddingTop;
          switch (eVertAlignment)
          {
            case TOP:
              fPaddingTop = 0f;
              break;
            case MIDDLE:
              fPaddingTop = (fUsedHeightFull - aElement.getFullYSum () - m_aPreparedColumnHeight[nIndex]) / 2;
              break;
            case BOTTOM:
              fPaddingTop = fUsedHeightFull - aElement.getFullYSum () - m_aPreparedColumnHeight[nIndex];
              break;
            default:
              throw new IllegalStateException ("Unsupported vertical alignment: " + eVertAlignment);
          }
          if (fPaddingTop != 0f && aElement instanceof AbstractPLElement )
          {
            final AbstractPLElement  aRealElement = (AbstractPLElement ) aElement;
            aRealElement.internalMarkAsNotPrepared ();
            aRealElement.setPaddingTop (aRealElement.getPaddingTop () + fPaddingTop);
            aRealElement.internalMarkAsPrepared (new SizeSpec (m_aPreparedColumnWidth[nIndex],
                                                               m_aPreparedColumnHeight[nIndex] + fPaddingTop));
          }
        }
        ++nIndex;
      }
    }

    // Add at the end, because previously only the max was used
    fUsedHeightFull += fColumnBorderYSumWidth;

    // Small consistency check (with rounding included)
    if (GlobalDebug.isDebugMode ())
    {
      if (fUsedWidthFull - aCtx.getAvailableWidth () > 0.01)
        s_aLogger.warn (getDebugID () +
                        " uses more width (" +
                        fUsedWidthFull +
                        ") than available (" +
                        aCtx.getAvailableWidth () +
                        ")!");
      if (fUsedHeightFull - aCtx.getAvailableHeight () > 0.01)
        if (!isSplittable ())
          s_aLogger.warn (getDebugID () +
                          " uses more height (" +
                          fUsedHeightFull +
                          ") than available (" +
                          aCtx.getAvailableHeight () +
                          ")!");
    }

    return new SizeSpec (fUsedWidthFull, fUsedHeightFull);
  }

  @Override
  public void doPageSetup (@Nonnull final PageSetupContext aCtx)
  {
    for (final PLHBoxColumn aColumn : m_aColumns)
      aColumn.getElement ().doPageSetup (aCtx);
  }

  @Override
  protected void onPerform (@Nonnull final RenderingContext aCtx) throws IOException
  {
    final PDPageContentStreamWithCache aContentStream = aCtx.getContentStream ();
    final float fColumnBorderTopWidth = m_aColumnBorder.getTopWidth ();
    final float fColumnBorderLeftWidth = m_aColumnBorder.getLeftWidth ();
    final float fColumnBorderXSumWidth = m_aColumnBorder.getXSumWidth ();
    final float fColumnBorderYSumWidth = m_aColumnBorder.getYSumWidth ();

    float fCurX = aCtx.getStartLeft () + getPaddingLeft () + fColumnBorderLeftWidth;
    final float fCurY = aCtx.getStartTop () - getPaddingTop () - fColumnBorderTopWidth;
    final float fHBoxHeight = aCtx.getHeight () - getPaddingYSum () - fColumnBorderYSumWidth;

    int nIndex = 0;
    for (final PLHBoxColumn aColumn : m_aColumns)
    {
      final IPLRenderableObject  aElement = aColumn.getElement ();
      final float fItemWidth = m_aPreparedColumnWidth[nIndex];
      final float fItemHeight = m_aPreparedColumnHeight[nIndex];

      // apply special column borders - debug: blue
      {
        // Disregard the padding of this HBox!!!
        final float fLeft = fCurX;
        final float fTop = fCurY;
        final float fWidth = fItemWidth + aElement.getFullXSum ();
        final float fHeight = fHBoxHeight;

        // Fill before border
        Color aFillColor = aColumn.getFillColor ();
        if (aFillColor == null)
          aFillColor = m_aColumnFillColor;
        if (aFillColor != null)
        {
          aContentStream.setNonStrokingColor (aFillColor);
          aContentStream.fillRect (fLeft, fTop - fHeight, fWidth, fHeight);
        }

        BorderSpec aRealBorder = m_aColumnBorder;
        if (PLRenderHelper.shouldApplyDebugBorder (aRealBorder, aCtx.isDebugMode ()))
          aRealBorder = new BorderSpec (new BorderStyleSpec (PLDebug.BORDER_COLOR_HBOX));
        if (aRealBorder.hasAnyBorder ())
          PLRenderHelper.renderBorder (this, aContentStream, fLeft, fTop, fWidth, fHeight, aRealBorder);
      }

      // Perform contained element after border
      float fStartLeft = fCurX;
      float fStartTop = fCurY;
      float fItemWidthWithPadding = fItemWidth;
      float fItemHeightWithPadding = fItemHeight;
      if (aElement instanceof IPLElement )
      {
        final IPLElement  aRealElement = (IPLElement ) aElement;
        fStartLeft += aRealElement.getMarginAndBorderLeft ();
        fStartTop -= aRealElement.getMarginAndBorderTop ();
        fItemWidthWithPadding += aRealElement.getPaddingXSum ();
        fItemHeightWithPadding += aRealElement.getPaddingYSum ();
      }
      final RenderingContext aItemCtx = new RenderingContext (aCtx,
                                                              fStartLeft,
                                                              fStartTop,
                                                              fItemWidthWithPadding,
                                                              fItemHeightWithPadding);
      aElement.perform (aItemCtx);

      // Update X-pos
      fCurX += fItemWidth + aElement.getFullXSum () + fColumnBorderXSumWidth;
      ++nIndex;
    }
  }

  @Override
  public String toString ()
  {
    return ToStringGenerator.getDerived (super.toString ())
                            .append ("columns", m_aColumns)
                            .append ("startWidthItems", m_nStarWidthItems)
                            .append ("columnBorder", m_aColumnBorder)
                            .appendIfNotNull ("columnFillColor", m_aColumnFillColor)
                            .appendIfNotNull ("preparedWidth", m_aPreparedColumnWidth)
                            .appendIfNotNull ("preparedHeight", m_aPreparedColumnHeight)
                            .toString ();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy