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

com.helger.pdflayout.element.text.AbstractPLText Maven / Gradle / Ivy

There is a newer version: 7.3.5
Show newest version
/**
 * 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.text;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

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

import com.helger.commons.CGlobal;
import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.CodingStyleguideUnaware;
import com.helger.commons.annotation.Nonempty;
import com.helger.commons.annotation.OverrideOnDemand;
import com.helger.commons.annotation.ReturnsMutableCopy;
import com.helger.commons.collection.ext.CommonsArrayList;
import com.helger.commons.collection.ext.ICommonsList;
import com.helger.commons.string.StringHelper;
import com.helger.commons.string.ToStringGenerator;
import com.helger.pdflayout.base.AbstractPLAlignedElement;
import com.helger.pdflayout.base.PLElementWithSize;
import com.helger.pdflayout.pdfbox.PDPageContentStreamWithCache;
import com.helger.pdflayout.render.PreparationContext;
import com.helger.pdflayout.render.RenderingContext;
import com.helger.pdflayout.spec.FontSpec;
import com.helger.pdflayout.spec.LoadedFont;
import com.helger.pdflayout.spec.SizeSpec;
import com.helger.pdflayout.spec.TextAndWidthSpec;

/**
 * Render text
 *
 * @author Philip Helger
 * @param 
 *        Implementation type
 */
public abstract class AbstractPLText >
                                     extends AbstractPLAlignedElement 
{
  public static final boolean DEFAULT_TOP_DOWN = true;
  public static final int DEFAULT_MAX_ROWS = CGlobal.ILLEGAL_UINT;

  private final String m_sText;
  private final FontSpec m_aFontSpec;
  private boolean m_bTopDown = DEFAULT_TOP_DOWN;
  private int m_nMaxRows = DEFAULT_MAX_ROWS;

  // prepare result
  private LoadedFont m_aLoadedFont;
  protected int m_nPreparedLineCountUnmodified = CGlobal.ILLEGAL_UINT;
  protected ICommonsList  m_aPreparedLinesUnmodified;
  @CodingStyleguideUnaware
  protected List  m_aPreparedLines;
  protected float m_fLineHeight;

  public AbstractPLText (@Nullable final String sText, @Nonnull final FontSpec aFontSpec)
  {
    if (StringHelper.hasNoText (sText))
    {
      m_sText = "";
    }
    else
    {
      // Unify line endings so that all "\r" are removed and only "\n" is
      // contained
      String sCleaned = sText;
      sCleaned = StringHelper.replaceAll (sCleaned, "\r\n", "\n");
      sCleaned = StringHelper.replaceAll (sCleaned, '\r', '\n');
      m_sText = sCleaned;
    }
    m_aFontSpec = ValueEnforcer.notNull (aFontSpec, "FontSpec");
  }

  @Nonnull
  public String getText ()
  {
    return m_sText;
  }

  public boolean hasText ()
  {
    return m_sText.length () > 0;
  }

  public boolean hasNoText ()
  {
    return m_sText.length () == 0;
  }

  @Nonnull
  public FontSpec getFontSpec ()
  {
    return m_aFontSpec;
  }

  @Nonnull
  @OverridingMethodsMustInvokeSuper
  public IMPLTYPE setBasicDataFrom (@Nonnull final AbstractPLText  aSource)
  {
    super.setBasicDataFrom (aSource);
    setTopDown (aSource.m_bTopDown);
    setMaxRows (aSource.m_nMaxRows);
    return thisAsT ();
  }

  /**
   * @return true if the text is rendered from top to bottom, or
   *         false if the text is rendered from bottom to top. The
   *         default value is {@link #DEFAULT_TOP_DOWN}.
   */
  public boolean isTopDown ()
  {
    return m_bTopDown;
  }

  /**
   * Set the rendering direction: top-down or bottom-up.
   *
   * @param bTopDown
   *        true to render top-down, false to render
   *        bottom-up.
   * @return this
   */
  @Nonnull
  public IMPLTYPE setTopDown (final boolean bTopDown)
  {
    m_bTopDown = bTopDown;
    return thisAsT ();
  }

  /**
   * @return The maximum number of rows to be rendered. If this value is ≤ 0
   *         than all rows are rendered. The default value is
   *         {@link #DEFAULT_MAX_ROWS}.
   */
  @CheckForSigned
  public int getMaxRows ()
  {
    return m_nMaxRows;
  }

  /**
   * Set the maximum number of rows to render.
   *
   * @param nMaxRows
   *        Maximum number of rows. If ≤ 0 than all lines are rendered.
   * @return this
   */
  @Nonnull
  public IMPLTYPE setMaxRows (final int nMaxRows)
  {
    m_nMaxRows = nMaxRows;
    return thisAsT ();
  }

  final void internalSetPreparedLines (@Nonnull final ICommonsList  aLines)
  {
    final int nLines = aLines.size ();
    m_nPreparedLineCountUnmodified = nLines;
    m_aPreparedLinesUnmodified = aLines;
    if (m_nMaxRows <= 0)
    {
      // Use all lines
      m_aPreparedLines = aLines;
    }
    else
    {
      // Use only a certain maximum number of rows
      if (nLines <= m_nMaxRows)
      {
        // We have less lines than the maximum
        m_aPreparedLines = aLines;
      }
      else
      {
        // Maximum number of lines exceeded
        m_aPreparedLines = aLines.subList (0, m_nMaxRows);
      }
    }

    if (!m_bTopDown)
    {
      // Reverse order only once
      Collections.reverse (m_aPreparedLines);
    }
  }

  final void internalSetPreparedFontData (@Nonnull final LoadedFont aLoadedFont, final float fLineHeight)
  {
    ValueEnforcer.notNull (aLoadedFont, "LoadedFont");
    m_aLoadedFont = aLoadedFont;
    m_fLineHeight = fLineHeight;
  }

  @Override
  protected SizeSpec onPrepare (@Nonnull final PreparationContext aCtx) throws IOException
  {
    // Load font into document
    m_aLoadedFont = aCtx.getGlobalContext ().getLoadedFont (m_aFontSpec);
    final float fFontSize = m_aFontSpec.getFontSize ();
    m_fLineHeight = m_aLoadedFont.getLineHeight (fFontSize);

    if (hasNoText ())
    {
      // Nothing to do - empty
      // But keep the height distance!
      return new SizeSpec (0, m_fLineHeight);
    }

    // Split text into rows
    internalSetPreparedLines (m_aLoadedFont.getFitToWidth (m_sText, fFontSize, aCtx.getAvailableWidth ()));

    // Determine height by number of lines
    return new SizeSpec (aCtx.getAvailableWidth (), m_aPreparedLines.size () * m_fLineHeight);
  }

  /**
   * @return The total number of prepared lines, not taking the maxRows into
   *         consideration. Always ≥ 0.
   */
  @Nonnegative
  public int getPreparedLineCountUnmodified ()
  {
    if (m_nPreparedLineCountUnmodified == CGlobal.ILLEGAL_UINT)
      throw new IllegalStateException ("Preparation is not yet done");
    return m_nPreparedLineCountUnmodified;
  }

  @Nonnull
  @ReturnsMutableCopy
  public ICommonsList  getAllPreparedLinesUnmodified ()
  {
    if (m_aPreparedLinesUnmodified == null)
      throw new IllegalStateException ("Preparation is not yet done");
    return new CommonsArrayList<> (m_aPreparedLinesUnmodified);
  }

  /**
   * Get the text to draw, in case it is different from the stored text (e.g.
   * for page numbers in {@link PLTextWithPlaceholders})
   *
   * @param sText
   *        Original text. Never null.
   * @param aCtx
   *        The current rendering context. Never null.
   * @return The real text to draw. May not be null.
   */
  @Nonnull
  @OverrideOnDemand
  protected String getTextToDraw (@Nonnull final String sText, @Nonnull final RenderingContext aCtx)
  {
    return sText;
  }

  @Override
  protected void onPerform (@Nonnull final RenderingContext aCtx) throws IOException
  {
    if (hasNoText ())
    {
      // Nothing to do - empty text
      return;
    }

    final PDPageContentStreamWithCache aContentStream = aCtx.getContentStream ();
    aContentStream.beginText ();

    // Set font if changed
    aContentStream.setFont (m_aLoadedFont, m_aFontSpec);

    final float fFontSize = m_aFontSpec.getFontSize ();
    final float fLineHeight = m_fLineHeight;

    final float fLeft = getPaddingLeft ();
    final float fUsableWidth = aCtx.getWidth () - getPaddingXSum ();
    final float fTop = getPaddingTop ();
    int nIndex = 0;
    final int nMax = m_aPreparedLines.size ();
    for (final TextAndWidthSpec aTW : m_aPreparedLines)
    {
      // Replace text (if any)
      float fWidth = aTW.getWidth ();
      final String sOrigText = aTW.getText ();

      // get the real text to draw
      final String sDrawText = getTextToDraw (sOrigText, aCtx);
      if (!sOrigText.equals (sDrawText))
      {
        // Text changed - recalculate width!
        fWidth = m_aLoadedFont.getStringWidth (sDrawText, fFontSize);
      }

      float fIndentX;
      switch (getHorzAlign ())
      {
        case LEFT:
          fIndentX = fLeft;
          break;
        case CENTER:
          fIndentX = fLeft + (fUsableWidth - fWidth) / 2;
          break;
        case RIGHT:
          fIndentX = fLeft + fUsableWidth - fWidth;
          break;
        default:
          throw new IllegalStateException ("Unsupported horizontal alignment " + getHorzAlign ());
      }

      if (nIndex == 0)
      {
        // Initial move - only partial line height!
        aContentStream.moveTextPositionByAmount (aCtx.getStartLeft () +
                                                 fIndentX,
                                                 aCtx.getStartTop () - fTop - (fLineHeight * 0.75f));
      }
      else
        if (fIndentX != 0)
        {
          // Indent subsequent line
          aContentStream.moveTextPositionByAmount (fIndentX, 0);
        }

      // Main draw string
      aContentStream.drawString (sDrawText);
      ++nIndex;

      // Goto next line
      if (nIndex < nMax)
      {
        if (m_bTopDown)
        {
          // Outdent and one line down, except for last line
          aContentStream.moveTextPositionByAmount (-fIndentX, -fLineHeight);
        }
        else
        {
          // Outdent and one line up, except for last line
          aContentStream.moveTextPositionByAmount (-fIndentX, fLineHeight);
        }
      }
    }
    aContentStream.endText ();
  }

  protected final float getDisplayHeightOfLines (@Nonnegative final int nLineCount)
  {
    // Note: when drawing the text, only 0.75*lineHeight is subtracted so now we
    // need to add 0.25*lineHeight so that it looks good.
    return (nLineCount + 0.25f) * m_fLineHeight;
  }

  @Nonnull
  public PLElementWithSize getCopy (final float fElementWidth,
                                    @Nonnull @Nonempty final List  aLines,
                                    final boolean bSplittableCopy)
  {
    ValueEnforcer.notEmpty (aLines, "Lines");

    // Create a copy to be independent!
    final ICommonsList  aLineCopy = new CommonsArrayList<> (aLines);

    // Excluding padding/margin
    final SizeSpec aSize = new SizeSpec (fElementWidth, getDisplayHeightOfLines (aLineCopy.size ()));

    final String sTextContent = TextAndWidthSpec.getAsText (aLineCopy);
    final AbstractPLText  aNewText = bSplittableCopy ? new PLTextSplittable (sTextContent, getFontSpec ())
                                                        : new PLText (sTextContent, getFontSpec ());
    aNewText.setBasicDataFrom (this).internalMarkAsPrepared (aSize);
    aNewText.internalSetPreparedLines (aLineCopy);
    aNewText.internalSetPreparedFontData (m_aLoadedFont, m_fLineHeight);

    return new PLElementWithSize (aNewText, aSize);
  }

  @Override
  public String toString ()
  {
    return ToStringGenerator.getDerived (super.toString ())
                            .append ("Text", m_sText)
                            .append ("FontSpec", m_aFontSpec)
                            .append ("TopDown", m_bTopDown)
                            .append ("MaxRows", m_nMaxRows)
                            .toString ();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy