Package org.pentaho.reporting.engine.classic.core.modules.output.pageable.graphics.internal

Source Code of org.pentaho.reporting.engine.classic.core.modules.output.pageable.graphics.internal.LogicalPageDrawable

/*
* 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 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors..  All rights reserved.
*/

package org.pentaho.reporting.engine.classic.core.modules.output.pageable.graphics.internal;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.print.PageFormat;
import java.awt.print.Paper;

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.LocalImageContainer;
import org.pentaho.reporting.engine.classic.core.URLImageContainer;
import org.pentaho.reporting.engine.classic.core.layout.ModelPrinter;
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.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.RenderableComplexText;
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.table.TableCellRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.table.TableColumnGroupNode;
import org.pentaho.reporting.engine.classic.core.layout.model.table.TableRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.table.TableRowRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.table.TableSectionRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.output.CollectSelectedNodesStep;
import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorFeature;
import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorMetaData;
import org.pentaho.reporting.engine.classic.core.layout.output.RenderUtility;
import org.pentaho.reporting.engine.classic.core.layout.process.IterateStructuralProcessStep;
import org.pentaho.reporting.engine.classic.core.layout.process.RevalidateTextEllipseProcessStep;
import org.pentaho.reporting.engine.classic.core.layout.text.ExtendedBaselineInfo;
import org.pentaho.reporting.engine.classic.core.layout.text.Glyph;
import org.pentaho.reporting.engine.classic.core.layout.text.GlyphList;
import org.pentaho.reporting.engine.classic.core.modules.output.pageable.graphics.PageDrawable;
import org.pentaho.reporting.engine.classic.core.style.BandStyleKeys;
import org.pentaho.reporting.engine.classic.core.style.ElementStyleKeys;
import org.pentaho.reporting.engine.classic.core.style.StyleKey;
import org.pentaho.reporting.engine.classic.core.style.StyleSheet;
import org.pentaho.reporting.engine.classic.core.style.TextStyleKeys;
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.base.util.FastStack;
import org.pentaho.reporting.libraries.base.util.WaitingImageObserver;
import org.pentaho.reporting.libraries.fonts.encoding.CodePointBuffer;
import org.pentaho.reporting.libraries.resourceloader.Resource;
import org.pentaho.reporting.libraries.resourceloader.ResourceException;
import org.pentaho.reporting.libraries.resourceloader.ResourceKey;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
import org.pentaho.reporting.libraries.resourceloader.factory.drawable.DrawableWrapper;

/**
* The page drawable is the content provider for the Graphics2DOutputTarget. This component is responsible for rendering
* the current page to a Graphics2D object.
*
* @author Thomas Morgner
*/
@SuppressWarnings("HardCodedStringLiteral")
public class LogicalPageDrawable extends IterateStructuralProcessStep implements PageDrawable
{
  protected static class TextSpec
  {
    private boolean bold;
    private boolean italics;
    private String fontName;
    private float fontSize;
    private Graphics2D graphics;

    protected TextSpec(final StyleSheet layoutContext,
                       final OutputProcessorMetaData metaData,
                       final Graphics2D graphics)
    {
      if (graphics == null)
      {
        throw new NullPointerException();
      }
      if (metaData == null)
      {
        throw new NullPointerException();
      }
      if (layoutContext == null)
      {
        throw new NullPointerException();
      }

      this.graphics = graphics;
      fontName = metaData.getNormalizedFontFamilyName
          ((String) layoutContext.getStyleProperty(TextStyleKeys.FONT));
      fontSize = (float) layoutContext.getDoubleStyleProperty(TextStyleKeys.FONTSIZE, 10);
      bold = layoutContext.getBooleanStyleProperty(TextStyleKeys.BOLD);
      italics = layoutContext.getBooleanStyleProperty(TextStyleKeys.ITALIC);
    }

    public boolean isBold()
    {
      return bold;
    }

    public boolean isItalics()
    {
      return italics;
    }

    public String getFontName()
    {
      return fontName;
    }

    public float getFontSize()
    {
      return fontSize;
    }

    public Graphics2D getGraphics()
    {
      return graphics;
    }

    public void close()
    {
      graphics.dispose();
      graphics = null;
    }
  }

  private static class FontDecorationSpec
  {
    private double end;
    private double start;
    private double verticalPosition;
    private double lineWidth;
    private Color textColor;

    protected FontDecorationSpec()
    {
      start = -1;
      end = -1;
    }

    public Color getTextColor()
    {
      return textColor;
    }

    public void setTextColor(final Color textColor)
    {
      this.textColor = textColor;
    }

    public void updateStart(final double start)
    {
      if (this.start < 0)
      {
        this.start = start;
      }
      else if (start < this.start)
      {
        this.start = start;
      }
    }

    public double getEnd()
    {
      return end;
    }

    public void updateEnd(final double end)
    {
      if (this.end < 0)
      {
        this.end = end;
      }
      else if (end > this.end)
      {
        this.end = end;
      }
    }

    public double getStart()
    {
      return start;
    }

    public double getLineWidth()
    {
      return lineWidth;
    }

    public void updateLineWidth(final double lineWidth)
    {
      if (lineWidth > this.lineWidth)
      {
        this.lineWidth = lineWidth;
      }
    }

    public void updateVerticalPosition(final double verticalPosition)
    {
      if (verticalPosition > this.verticalPosition)
      {
        this.verticalPosition = verticalPosition;
      }
    }

    public double getVerticalPosition()
    {
      return verticalPosition;
    }
  }

  private static class TableContext
  {
    private TableContext parent;
    private StrictBounds bounds;
    private StrictBounds drawArea;

    private TableContext(final TableContext parent)
    {
      this.parent = parent;
      this.bounds = new StrictBounds();
      this.drawArea = new StrictBounds();
    }

    public StrictBounds getDrawArea()
    {
      return drawArea;
    }

    public StrictBounds getBounds()
    {
      return bounds;
    }

    public TableContext pop()
    {
      return parent;
    }
  }

  public static final BasicStroke DEFAULT_STROKE = new BasicStroke(1);
  private static final Log logger = LogFactory.getLog(LogicalPageDrawable.class);

  private FontDecorationSpec strikeThrough;
  private FontDecorationSpec underline;
  private boolean outlineMode;
  private LogicalPageBox rootBox;
  private OutputProcessorMetaData metaData;
  private PageFormat pageFormat;
  private double width;
  private double height;
  private CodePointBuffer codePointBuffer;
  private Graphics2D graphics;
  private boolean textLineOverflow;
  private long contentAreaX1;
  private long contentAreaX2;
  private RevalidateTextEllipseProcessStep revalidateTextEllipseProcessStep;
  private StrictBounds drawArea;
  // A reusable rectangle for rendering; not used for decisions
  private Rectangle2D.Double boxArea;
  private TextSpec textSpec;
  private boolean ellipseDrawn;
  private CollectSelectedNodesStep collectSelectedNodesStep;
  private BorderRenderer borderRenderer;
  private boolean drawPageBackground;
  private ResourceManager resourceManager;
  private boolean clipOnWordBoundary;
  private boolean strictClipping;
  private boolean unalignedPageBands;
  private TableContext tableContext;
  private FastStack<Graphics2D> graphicsContexts;
  private StrictBounds pageArea;

  public LogicalPageDrawable()
  {
    this.graphicsContexts = new FastStack<Graphics2D>();
    this.borderRenderer = new BorderRenderer();
    this.codePointBuffer = new CodePointBuffer(400);
    this.boxArea = new Rectangle2D.Double();
    this.drawPageBackground = true;
  }

  @Deprecated
  public LogicalPageDrawable(final LogicalPageBox rootBox,
                             final OutputProcessorMetaData metaData,
                             final ResourceManager resourceManager)
  {
    this();
    init(rootBox, metaData, resourceManager);
  }

  public void init(final LogicalPageBox rootBox,
                   final OutputProcessorMetaData metaData,
                   final ResourceManager resourceManager)
  {
    if (rootBox == null)
    {
      throw new NullPointerException();
    }
    if (metaData == null)
    {
      throw new NullPointerException();
    }
    if (resourceManager == null)
    {
      throw new NullPointerException();
    }

    this.resourceManager = resourceManager;
    this.metaData = metaData;
    this.rootBox = rootBox;
    this.width = StrictGeomUtility.toExternalValue(rootBox.getPageWidth());
    this.height = StrictGeomUtility.toExternalValue(rootBox.getPageHeight());

    final Paper paper = new Paper();
    paper.setImageableArea(0, 0, width, height);

    this.pageFormat = new PageFormat();
    this.pageFormat.setPaper(paper);

    this.strictClipping = "true".equals(metaData.getConfiguration().getConfigProperty
        ("org.pentaho.reporting.engine.classic.core.modules.output.pageable.graphics.StrictClipping"));
    this.outlineMode = "true".equals(metaData.getConfiguration().getConfigProperty
        ("org.pentaho.reporting.engine.classic.core.modules.output.pageable.graphics.debug.OutlineMode"));
    if ("true".equals(metaData.getConfiguration().getConfigProperty
        ("org.pentaho.reporting.engine.classic.core.modules.output.pageable.graphics.debug.PrintPageContents")))
    {
      ModelPrinter.INSTANCE.print(rootBox);
    }

    this.unalignedPageBands = metaData.isFeatureSupported(OutputProcessorFeature.UNALIGNED_PAGEBANDS);
    revalidateTextEllipseProcessStep = new RevalidateTextEllipseProcessStep(metaData);
    collectSelectedNodesStep = new CollectSelectedNodesStep();
    this.clipOnWordBoundary = "true".equals
        (metaData.getConfiguration().getConfigProperty(
            "org.pentaho.reporting.engine.classic.core.LastLineBreaksOnWordBoundary"));
  }

  public LogicalPageBox getLogicalPageBox()
  {
    return rootBox;
  }

  protected ResourceManager getResourceManager()
  {
    return resourceManager;
  }

  public boolean isClipOnWordBoundary()
  {
    return clipOnWordBoundary;
  }

  public boolean isOutlineMode()
  {
    return outlineMode;
  }

  public void setOutlineMode(final boolean outlineMode)
  {
    this.outlineMode = outlineMode;
  }

  protected StrictBounds getDrawArea()
  {
    return drawArea;
  }

  public PageFormat getPageFormat()
  {
    return (PageFormat) pageFormat.clone();
  }

  /**
   * Returns the preferred size of the drawable. If the drawable is aspect ratio aware, these bounds should be used to
   * compute the preferred aspect ratio for this drawable.
   *
   * @return the preferred size.
   */
  public Dimension getPreferredSize()
  {
    return new Dimension((int) width, (int) height);
  }

  public double getHeight()
  {
    return height;
  }

  public double getWidth()
  {
    return width;
  }

  /**
   * Returns true, if this drawable will preserve an aspect ratio during the drawing.
   *
   * @return true, if an aspect ratio is preserved, false otherwise.
   */
  @SuppressWarnings("UnusedDeclaration")
  public boolean isPreserveAspectRatio()
  {
    return true;
  }

  public boolean isDrawPageBackground()
  {
    return drawPageBackground;
  }

  public void setDrawPageBackground(final boolean drawPageBackground)
  {
    this.drawPageBackground = drawPageBackground;
  }

  /**
   * Draws the object.
   *
   * @param graphics the graphics device.
   * @param area     the area inside which the object should be drawn.
   */
  public void draw(final Graphics2D graphics, final Rectangle2D area)
  {
    final Graphics2D g2 = (Graphics2D) graphics.create();
    if (isDrawPageBackground())
    {
      g2.setPaint(Color.white);
      g2.fill(area);
    }
    g2.translate(-area.getX(), -area.getY());

    try
    {
      final StrictBounds pageBounds =
          StrictGeomUtility.createBounds(area.getX(), area.getY(), area.getWidth(), area.getHeight());
      this.pageArea = pageBounds;
      this.drawArea = pageBounds;
      this.graphics = g2;

      if (startBlockBox(rootBox))
      {
        processRootBand(pageBounds);
      }
      finishBlockBox(rootBox);
    }
    finally
    {
      this.graphics = null;
      this.drawArea = null;
      g2.dispose();
    }
  }

  protected void processRootBand(final StrictBounds pageBounds)
  {
    startProcessing(rootBox.getWatermarkArea());

    final BlockRenderBox headerArea = rootBox.getHeaderArea();
    final BlockRenderBox footerArea = rootBox.getFooterArea();
    final BlockRenderBox repeatFooterArea = rootBox.getRepeatFooterArea();
    final StrictBounds headerBounds =
        new StrictBounds(headerArea.getX(), headerArea.getY(), headerArea.getWidth(), headerArea.getHeight());
    final StrictBounds footerBounds =
        new StrictBounds(footerArea.getX(), footerArea.getY(), footerArea.getWidth(), footerArea.getHeight());
    final StrictBounds repeatFooterBounds = new StrictBounds
        (repeatFooterArea.getX(), repeatFooterArea.getY(), repeatFooterArea.getWidth(), repeatFooterArea.getHeight());
    final StrictBounds contentBounds = new StrictBounds
        (rootBox.getX(), headerArea.getY() + headerArea.getHeight(),
            rootBox.getWidth(), repeatFooterArea.getY() - headerArea.getHeight());

    final double headerHeight = StrictGeomUtility.toExternalValue(drawArea.getHeight());

    final Shape clip = this.graphics.getClip();
    setDrawArea(headerBounds);
    this.graphics.clip(createClipRect(drawArea));
    startProcessing(headerArea);

    if (unalignedPageBands)
    {
      this.graphics.translate(0, headerHeight);
    }

    setDrawArea(contentBounds);
    this.graphics.setClip(clip);
    this.graphics.clip(createClipRect(drawArea));
    processBoxChilds(rootBox);

    if (unalignedPageBands)
    {
      this.graphics.translate(0, -headerHeight);
      this.graphics.translate(0,
          height - StrictGeomUtility.toExternalValue(footerBounds.getHeight() + repeatFooterBounds.getHeight()));
    }

    setDrawArea(repeatFooterBounds);
    this.graphics.setClip(clip);
    this.graphics.clip(createClipRect(drawArea));
    startProcessing(repeatFooterArea);

    if (unalignedPageBands)
    {
      this.graphics.translate(0, StrictGeomUtility.toExternalValue(repeatFooterBounds.getHeight()));
    }
    setDrawArea(footerBounds);
    this.graphics.setClip(clip);
    this.graphics.clip(createClipRect(drawArea));
    startProcessing(footerArea);

    setDrawArea(pageBounds);
  }

  protected Rectangle2D createClipRect(final StrictBounds bounds)
  {
    return StrictGeomUtility.createAWTRectangle(bounds.getX() - 1, bounds.getY() - 1,
        bounds.getWidth() + 2, bounds.getHeight() + 2);
  }

  protected LogicalPageBox getRootBox()
  {
    return rootBox;
  }

  protected void setDrawArea(final StrictBounds drawArea)
  {
    this.drawArea = pageArea.createIntersection(drawArea);
  }

  protected void drawOutlineBox(final Graphics2D g2, final RenderBox box)
  {
    final int nodeType = box.getNodeType();
    if (nodeType == LayoutNodeTypes.TYPE_BOX_PARAGRAPH)
    {
      g2.setPaint(Color.magenta);
    }
    else if (nodeType == LayoutNodeTypes.TYPE_BOX_LINEBOX)
    {
      g2.setPaint(Color.orange);
    }
    else if ((nodeType & LayoutNodeTypes.MASK_BOX_TABLE) == LayoutNodeTypes.MASK_BOX_TABLE)
    {
      g2.setPaint(Color.cyan);
    }
    else
    {
      g2.setPaint(Color.lightGray);
    }
    final double x = StrictGeomUtility.toExternalValue(box.getX());
    final double y = StrictGeomUtility.toExternalValue(box.getY());
    final double w = StrictGeomUtility.toExternalValue(box.getWidth());
    final double h = StrictGeomUtility.toExternalValue(box.getHeight());
    boxArea.setFrame(x, y, w, h);
    g2.draw(boxArea);
  }

  protected void processLinksAndAnchors(final RenderNode box)
  {
    final StyleSheet styleSheet = box.getStyleSheet();
    final String target = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_TARGET);
    final String title = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_TITLE);
    if (target != null || title != null)
    {
      final String window = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_WINDOW);
      drawHyperlink(box, target, window, title);
    }

    final String anchor = (String) styleSheet.getStyleProperty(ElementStyleKeys.ANCHOR_NAME);
    if (anchor != null)
    {
      drawAnchor(box);
    }

    final String bookmark = (String) styleSheet.getStyleProperty(BandStyleKeys.BOOKMARK);
    if (bookmark != null)
    {
      drawBookmark(box, bookmark);
    }
  }

  protected void drawBookmark(final RenderNode box, final String bookmark)
  {
  }

  protected void drawHyperlink(final RenderNode box, final String target, final String window, final String title)
  {
  }

  public boolean startCanvasBox(final CanvasRenderBox box)
  {
    return startBox(box);
  }

  protected boolean startBlockBox(final BlockRenderBox box)
  {
    return startBox(box);
  }

  protected boolean startRowBox(final RenderBox box)
  {
    return startBox(box);
  }

  protected boolean startTableCellBox(final TableCellRenderBox box)
  {
    return startBox(box);
  }

  protected boolean startBox(final RenderBox box)
  {
    if (box.getStaticBoxLayoutProperties().isVisible() == false)
    {
      return false;
    }

    if (box instanceof LogicalPageBox == false)
    {
      if (box.isBoxVisible(drawArea) == false)
      {
        box.isBoxVisible(drawArea);
        return false;
      }
    }

    renderBoxBorderAndBackground(box);

    processLinksAndAnchors(box);
    return true;
  }

  protected boolean startTableRowBox(final TableRowRenderBox box)
  {
    return startBox(box);
  }

  protected boolean startTableSectionBox(final TableSectionRenderBox box)
  {
    if (box.getDisplayRole() != TableSectionRenderBox.Role.HEADER)
    {
      final StrictBounds bounds = tableContext.getBounds();
      if (bounds.getHeight() != 0)
      {
        // clip the printable area to an infinite large area below the header.
        // Pdf output has a limit of 32768 for its floating point numbers (16-bit),
        // any larger value yields an invalid clipping area.
        final StrictBounds clipBounds = new StrictBounds
            (bounds.getX(), bounds.getY() + bounds.getHeight(),
                StrictGeomUtility.toInternalValue(Short.MAX_VALUE),
                StrictGeomUtility.toInternalValue(Short.MAX_VALUE));
        clip(clipBounds);
        tableContext.getDrawArea().setRect(drawArea);
        drawArea.setRect(drawArea.createIntersection(clipBounds));
      }
    }
    return startBox(box);
  }

  protected void finishTableSectionBox(final TableSectionRenderBox box)
  {
    if (box.getDisplayRole() == TableSectionRenderBox.Role.HEADER)
    {
      tableContext.getBounds().setRect(box.getX(), box.getY(), box.getWidth(), box.getHeight());
    }
    else if (tableContext.getBounds().getHeight() != 0)
    {
      drawArea.setRect(tableContext.getDrawArea());
      clearClipping();
    }
  }

  protected boolean startTableBox(final TableRenderBox box)
  {
    tableContext = new TableContext(tableContext);
    return startBox(box);
  }

  protected void finishTableBox(final TableRenderBox box)
  {
    tableContext = tableContext.pop();
  }

  protected boolean startTableColumnGroupBox(final TableColumnGroupNode box)
  {
    return false;
  }

  protected boolean startAutoBox(final RenderBox box)
  {
    return startBox(box);
  }

  protected boolean startInlineBox(final InlineRenderBox box)
  {
    if (box.getStaticBoxLayoutProperties().isVisible() == false)
    {
      return false;
    }

    if (box.isBoxVisible(drawArea) == false)
    {
      return false;
    }

    renderBoxBorderAndBackground(box);

    TextSpec textSpec = getTextSpec();
    if (textSpec != null)
    {
      textSpec.close();
      setTextSpec(null);
    }

    final FontDecorationSpec newUnderlineSpec = computeUnderline(box, underline);
    if (underline != null && newUnderlineSpec == null)
    {
      drawTextDecoration(underline);
      underline = null;
    }
    else
    {
      underline = newUnderlineSpec;
    }

    final FontDecorationSpec newStrikeThroughSpec = computeStrikeThrough(box, strikeThrough);
    if (strikeThrough != null && newStrikeThroughSpec == null)
    {
      drawTextDecoration(strikeThrough);
      strikeThrough = null;
    }
    else
    {
      strikeThrough = newStrikeThroughSpec;
    }

    processLinksAndAnchors(box);
    return true;
  }

  protected boolean isIgnoreBorderWhenDrawingOutline()
  {
    return false;
  }

  protected void renderBoxBorderAndBackground(final RenderBox box)
  {
    final Graphics2D g2 = getGraphics();
    if (isOutlineMode())
    {
      drawOutlineBox(g2, box);
      if (isIgnoreBorderWhenDrawingOutline())
      {
        return;
      }
    }

    if (box.getBoxDefinition().getBorder().isEmpty() == false)
    {
      borderRenderer.paintBackgroundAndBorder(box, g2);
    }
    else
    {
      final Color backgroundColor = (Color) box.getStyleSheet().getStyleProperty(ElementStyleKeys.BACKGROUND_COLOR);
      if (backgroundColor != null)
      {
        final double x = StrictGeomUtility.toExternalValue(box.getX());
        final double y = StrictGeomUtility.toExternalValue(box.getY());
        final double w = StrictGeomUtility.toExternalValue(box.getWidth());
        final double h = StrictGeomUtility.toExternalValue(box.getHeight());
        boxArea.setFrame(x, y, w, h);
        g2.setColor(backgroundColor);
        g2.fill(boxArea);
      }
    }
  }

  protected Rectangle2D.Double getBoxArea()
  {
    return boxArea;
  }

  protected TextSpec getTextSpec()
  {
    return textSpec;
  }

  protected void setTextSpec(final TextSpec textSpec)
  {
    this.textSpec = textSpec;
  }

  private FontDecorationSpec computeUnderline(final RenderBox box, FontDecorationSpec oldSpec)
  {
    final StyleSheet styleSheet = box.getStyleSheet();
    if (styleSheet.getBooleanStyleProperty(TextStyleKeys.UNDERLINED) == false)
    {
      return null;
    }
    if (oldSpec == null)
    {
      oldSpec = new FontDecorationSpec();
    }
    final double size = box.getStyleSheet().getDoubleStyleProperty(TextStyleKeys.FONTSIZE, 0);
    final double lineWidth = Math.max(1, size / 20.0);
    oldSpec.updateLineWidth(lineWidth);
    oldSpec.setTextColor((Color) box.getStyleSheet().getStyleProperty(ElementStyleKeys.PAINT));
    return oldSpec;
  }

  private FontDecorationSpec computeStrikeThrough(final RenderBox box, FontDecorationSpec oldSpec)
  {
    final StyleSheet styleSheet = box.getStyleSheet();
    if (styleSheet.getBooleanStyleProperty(TextStyleKeys.STRIKETHROUGH) == false)
    {
      return null;
    }
    if (oldSpec == null)
    {
      oldSpec = new FontDecorationSpec();
    }

    final double size = box.getStyleSheet().getDoubleStyleProperty(TextStyleKeys.FONTSIZE, 0);
    final double lineWidth = Math.max(1, size / 20.0);
    oldSpec.updateLineWidth(lineWidth);
    oldSpec.setTextColor((Color) box.getStyleSheet().getStyleProperty(ElementStyleKeys.PAINT));
    return oldSpec;
  }

  private boolean isStyleActive(final StyleKey key, final RenderBox parent)
  {
    if ((parent.getLayoutNodeType() & LayoutNodeTypes.MASK_BOX_INLINE) != LayoutNodeTypes.MASK_BOX_INLINE)
    {
      return false;
    }
    return parent.getStyleSheet().getBooleanStyleProperty(key);
  }

  protected void finishInlineBox(final InlineRenderBox box)
  {
    final RenderBox parent = box.getParent();
    if (underline != null)
    {
      if (isStyleActive(TextStyleKeys.UNDERLINED, parent) == false)
      {
        // The parent has no underline style, but this box has. So finish up the underline.
        drawTextDecoration(underline);
        underline = null;
      }
    }
    else
    {
      // maybe this inlinebox has no underline, but the parent has ...
      underline = computeUnderline(box, null);
    }

    if (strikeThrough != null)
    {
      if (isStyleActive(TextStyleKeys.STRIKETHROUGH, parent) == false)
      {
        // The parent has no underline style, but this box has. So finish up the underline.
        drawTextDecoration(strikeThrough);
        strikeThrough = null;
      }
    }
    else
    {
      underline = computeUnderline(box, null);
    }

    TextSpec textSpec = getTextSpec();
    if (textSpec != null)
    {
      textSpec.close();
      setTextSpec(null);
    }
  }

  private void drawTextDecoration(final FontDecorationSpec decorationSpec)
  {
    final Graphics2D graphics = (Graphics2D) getGraphics().create();
    graphics.setColor(decorationSpec.getTextColor());
    graphics.setStroke(new BasicStroke((float) decorationSpec.getLineWidth()));
    graphics.draw(new Line2D.Double(decorationSpec.getStart(), decorationSpec.getVerticalPosition(),
        decorationSpec.getEnd(), decorationSpec.getVerticalPosition()));
    graphics.dispose();
  }

  protected void processParagraphChilds(final ParagraphRenderBox box)
  {
    this.contentAreaX1 = box.getContentAreaX1();
    this.contentAreaX2 = box.getContentAreaX2();
    this.textSpec = null;

    RenderBox lineBox = (RenderBox) box.getFirstChild();
    while (lineBox != null)
    {
      processTextLine(lineBox, contentAreaX1, contentAreaX2);
      lineBox = (RenderBox) lineBox.getNext();
    }

    if (textSpec != null)
    {
      throw new IllegalStateException();
    }
  }

  protected void processTextLine(final RenderBox lineBox,
                                 final long contentAreaX1,
                                 final long contentAreaX2)
  {
    if (lineBox.isNodeVisible(drawArea) == false)
    {
      return;
    }

    final boolean overflowProperty = lineBox.getParent().getStaticBoxLayoutProperties().isOverflowX();
    this.textLineOverflow = ((lineBox.getX() + lineBox.getWidth()) > contentAreaX2) && overflowProperty == false;

    this.ellipseDrawn = false;
    if (textLineOverflow)
    {
      revalidateTextEllipseProcessStep.compute(lineBox, contentAreaX1, contentAreaX2);
    }

    underline = null;
    strikeThrough = null;

    startProcessing(lineBox);
  }

  public long getContentAreaX2()
  {
    return contentAreaX2;
  }

  public void setContentAreaX2(final long contentAreaX2)
  {
    this.contentAreaX2 = contentAreaX2;
  }

  public long getContentAreaX1()
  {
    return contentAreaX1;
  }

  public void setContentAreaX1(final long contentAreaX1)
  {
    this.contentAreaX1 = contentAreaX1;
  }

  public boolean isTextLineOverflow()
  {
    return textLineOverflow;
  }

  public void setTextLineOverflow(final boolean textLineOverflow)
  {
    this.textLineOverflow = textLineOverflow;
  }


  protected void processOtherNode(final RenderNode node)
  {
    if (node.isNodeVisible(drawArea) == false)
    {
      return;
    }

    final int type = node.getNodeType();
    if (isTextLineOverflow())
    {
      if (node.isVirtualNode())
      {
        if (ellipseDrawn == false)
        {
          if (isClipOnWordBoundary() == false && type == LayoutNodeTypes.TYPE_NODE_TEXT)
          {
            final RenderableText text = (RenderableText) node;
            final long ellipseSize = extractEllipseSize(node);
            final long x1 = text.getX();
            final long effectiveAreaX2 = (contentAreaX2 - ellipseSize);

            if (x1 < contentAreaX2)
            {
              // The text node that is printed will overlap with the ellipse we need to print.
              drawText(text, effectiveAreaX2);
            }
          }
          else if (isClipOnWordBoundary() == false && type == LayoutNodeTypes.TYPE_NODE_COMPLEX_TEXT)
          {
            final RenderableComplexText text = (RenderableComplexText) node;
            //final long ellipseSize = extractEllipseSize(node);
            final long x1 = text.getX();
            //final long effectiveAreaX2 = (contentAreaX2 - ellipseSize);

            if (x1 < contentAreaX2)
            {
              // The text node that is printed will overlap with the ellipse we need to print.
              final Graphics2D g2;
              if (getTextSpec() == null)
              {
                g2 = (Graphics2D) getGraphics().create();
                final StyleSheet layoutContext = text.getStyleSheet();
                configureGraphics(layoutContext, g2);
                g2.setStroke(LogicalPageDrawable.DEFAULT_STROKE);

                if (RenderUtility.isFontSmooth(layoutContext, metaData))
                {
                  g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
                }
                else
                {
                  g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
                }
              }
              else
              {
                g2 = getTextSpec().getGraphics();
              }

              drawComplexText(text, g2);
            }
          }

          ellipseDrawn = true;

          final RenderBox parent = node.getParent();
          if (parent != null)
          {
            final RenderBox textEllipseBox = parent.getTextEllipseBox();
            if (textEllipseBox != null)
            {
              processBoxChilds(textEllipseBox);
            }
          }
          return;
        }
      }
    }

    if (type == LayoutNodeTypes.TYPE_NODE_TEXT)
    {
      final RenderableText text = (RenderableText) node;
      if (underline != null)
      {
        final ExtendedBaselineInfo baselineInfo = text.getBaselineInfo();
        final long underlinePos = text.getY() + baselineInfo.getUnderlinePosition();
        underline.updateVerticalPosition(StrictGeomUtility.toExternalValue(underlinePos));
        underline.updateStart(StrictGeomUtility.toExternalValue(text.getX()));
        underline.updateEnd(StrictGeomUtility.toExternalValue(text.getX() + text.getWidth()));
      }

      if (strikeThrough != null)
      {
        final ExtendedBaselineInfo baselineInfo = text.getBaselineInfo();
        final long strikethroughPos = text.getY() + baselineInfo.getStrikethroughPosition();
        strikeThrough.updateVerticalPosition(StrictGeomUtility.toExternalValue(strikethroughPos));
        strikeThrough.updateStart(StrictGeomUtility.toExternalValue(text.getX()));
        strikeThrough.updateEnd(StrictGeomUtility.toExternalValue(text.getX() + text.getWidth()));
      }

      if (isTextLineOverflow())
      {
        final long ellipseSize = extractEllipseSize(node);
        final long x1 = text.getX();
        final long x2 = x1 + text.getWidth();
        final long effectiveAreaX2 = (contentAreaX2 - ellipseSize);
        if (x2 <= effectiveAreaX2)
        {
          // the text will be fully visible.
          drawText(text);
        }
        else
        {
          if (x1 >= contentAreaX2)
          {
            // Skip, the node will not be visible.
          }
          else
          {
            // The text node that is printed will overlap with the ellipse we need to print.
            drawText(text, effectiveAreaX2);
          }

          final RenderBox parent = node.getParent();
          if (parent != null)
          {
            final RenderBox textEllipseBox = parent.getTextEllipseBox();
            if (textEllipseBox != null)
            {
              processBoxChilds(textEllipseBox);
            }
          }

          ellipseDrawn = true;
        }
      }
      else
      {
        drawText(text);
      }
    }
    else if (type == LayoutNodeTypes.TYPE_NODE_COMPLEX_TEXT)
    {
      final RenderableComplexText text = (RenderableComplexText) node;
      final long x1 = text.getX();

      if (x1 >= contentAreaX2)
      {
        // Skip, the node will not be visible.
      }
      else
      {
        // The text node that is printed will overlap with the ellipse we need to print.
        final Graphics2D g2;
        if (getTextSpec() == null)
        {
          g2 = (Graphics2D) getGraphics().create();
          final StyleSheet layoutContext = text.getStyleSheet();
          configureGraphics(layoutContext, g2);
          g2.setStroke(LogicalPageDrawable.DEFAULT_STROKE);

          if (RenderUtility.isFontSmooth(layoutContext, metaData))
          {
            g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
          }
          else
          {
            g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
          }
        }
        else
        {
          g2 = getTextSpec().getGraphics();
        }

        drawComplexText(text, g2);
      }
    }
  }

  protected void processRenderableContent(final RenderableReplacedContentBox box)
  {
    if (box.getStaticBoxLayoutProperties().isVisible() == false)
    {
      return;
    }

    if (box.isBoxVisible(drawArea) == false)
    {
      return;
    }

    renderBoxBorderAndBackground(box);
    processLinksAndAnchors(box);
    drawReplacedContent(box);
  }

  private long extractEllipseSize(final RenderNode node)
  {
    if (node == null)
    {
      return 0;
    }
    final RenderBox parent = node.getParent();
    if (parent == null)
    {
      return 0;
    }
    final RenderBox textEllipseBox = parent.getTextEllipseBox();
    if (textEllipseBox == null)
    {
      return 0;
    }
    return textEllipseBox.getWidth();
  }

  protected void drawReplacedContent(final RenderableReplacedContentBox content)
  {
    final Graphics2D g2 = getGraphics();
    final Object o = content.getContent().getRawObject();
    if (o instanceof Image)
    {
      drawImage(content, (Image) o);
    }
    else if (o instanceof DrawableWrapper)
    {
      final DrawableWrapper d = (DrawableWrapper) o;
      drawDrawable(content, g2, d);
    }
    else if (o instanceof LocalImageContainer)
    {
      final LocalImageContainer imageContainer = (LocalImageContainer) o;
      final Image image = imageContainer.getImage();
      drawImage(content, image);
    }
    else if (o instanceof URLImageContainer)
    {
      final URLImageContainer imageContainer = (URLImageContainer) o;
      if (imageContainer.isLoadable() == false)
      {
        LogicalPageDrawable.logger.info("URL-image cannot be rendered, as it was declared to be not loadable.");
        return;
      }

      final ResourceKey sourceURL = imageContainer.getResourceKey();
      if (sourceURL == null)
      {
        LogicalPageDrawable.logger.info("URL-image cannot be rendered, as it did not return a valid URL.");
      }

      try
      {
        final Resource resource = resourceManager.create(sourceURL, null, Image.class);
        final Image image = (Image) resource.getResource();
        drawImage(content, image);
      }
      catch (ResourceException e)
      {
        LogicalPageDrawable.logger.info("URL-image cannot be rendered, as the image was not loadable.", e);
      }
    }
    else
    {
      LogicalPageDrawable.logger.debug("Unable to handle " + o);
    }
  }

  /**
   * To be overriden in the PDF drawable.
   *
   * @param content the render-node that defines the anchor.
   */
  protected void drawAnchor(final RenderNode content)
  {

  }

  /**
   * @param content
   * @param image
   */
  protected boolean drawImage(final RenderableReplacedContentBox content, Image image)
  {
    final StyleSheet layoutContext = content.getStyleSheet();
    final boolean shouldScale = layoutContext.getBooleanStyleProperty(ElementStyleKeys.SCALE);

    final int x = (int) StrictGeomUtility.toExternalValue(content.getX());
    final int y = (int) StrictGeomUtility.toExternalValue(content.getY());
    final int width = (int) StrictGeomUtility.toExternalValue(content.getWidth());
    final int height = (int) StrictGeomUtility.toExternalValue(content.getHeight());

    if (width == 0 || height == 0)
    {
      LogicalPageDrawable.logger.debug("Error: Image area is empty: " + content);
      return false;
    }

    WaitingImageObserver obs = new WaitingImageObserver(image);
    obs.waitImageLoaded();
    final int imageWidth = image.getWidth(obs);
    final int imageHeight = image.getHeight(obs);
    if (imageWidth < 1 || imageHeight < 1)
    {
      return false;
    }

    final Rectangle2D.Double drawAreaBounds = new Rectangle2D.Double(x, y, width, height);
    final AffineTransform scaleTransform;

    final Graphics2D g2;
    if (shouldScale == false)
    {
      double deviceScaleFactor = 1;
      final double devResolution = metaData.getNumericFeatureValue(OutputProcessorFeature.DEVICE_RESOLUTION);
      if (metaData.isFeatureSupported(OutputProcessorFeature.IMAGE_RESOLUTION_MAPPING))
      {
        if (devResolution != 72.0 && devResolution > 0)
        {
          // Need to scale the device to its native resolution before attempting to draw the image..
          deviceScaleFactor = (72.0 / devResolution);
        }
      }

      final int clipWidth = Math.min(width, (int) Math.ceil(deviceScaleFactor * imageWidth));
      final int clipHeight = Math.min(height, (int) Math.ceil(deviceScaleFactor * imageHeight));
      final ElementAlignment horizontalAlignment =
          (ElementAlignment) layoutContext.getStyleProperty(ElementStyleKeys.ALIGNMENT);
      final ElementAlignment verticalAlignment =
          (ElementAlignment) layoutContext.getStyleProperty(ElementStyleKeys.VALIGNMENT);
      final int alignmentX = (int) RenderUtility.computeHorizontalAlignment(horizontalAlignment, width, clipWidth);
      final int alignmentY = (int) RenderUtility.computeVerticalAlignment(verticalAlignment, height, clipHeight);

      g2 = (Graphics2D) getGraphics().create();
      g2.clip(drawAreaBounds);
      g2.translate(x, y);
      g2.translate(alignmentX, alignmentY);
      g2.clip(new Rectangle2D.Float(0, 0, clipWidth, clipHeight));
      g2.scale(deviceScaleFactor, deviceScaleFactor);

      scaleTransform = null;
    }
    else
    {
      g2 = (Graphics2D) getGraphics().create();
      g2.clip(drawAreaBounds);
      g2.translate(x, y);
      g2.clip(new Rectangle2D.Float(0, 0, width, height));

      final double scaleX;
      final double scaleY;

      final boolean keepAspectRatio = layoutContext.getBooleanStyleProperty(ElementStyleKeys.KEEP_ASPECT_RATIO);
      if (keepAspectRatio)
      {
        final double scaleFactor = Math.min(width / (double) imageWidth, height / (double) imageHeight);
        scaleX = scaleFactor;
        scaleY = scaleFactor;
      }
      else
      {
        scaleX = width / (double) imageWidth;
        scaleY = height / (double) imageHeight;
      }

      final int clipWidth = (int) (scaleX * imageWidth);
      final int clipHeight = (int) (scaleY * imageHeight);

      final ElementAlignment horizontalAlignment =
          (ElementAlignment) layoutContext.getStyleProperty(ElementStyleKeys.ALIGNMENT);
      final ElementAlignment verticalAlignment =
          (ElementAlignment) layoutContext.getStyleProperty(ElementStyleKeys.VALIGNMENT);
      final int alignmentX = (int) RenderUtility.computeHorizontalAlignment(horizontalAlignment, width, clipWidth);
      final int alignmentY = (int) RenderUtility.computeVerticalAlignment(verticalAlignment, height, clipHeight);

      g2.translate(alignmentX, alignmentY);

      final Object contentCached = content.getContent().getContentCached();
      if (contentCached instanceof Image)
      {
        image = (Image) contentCached;
        scaleTransform = null;
      }
      else if (metaData.isFeatureSupported(OutputProcessorFeature.PREFER_NATIVE_SCALING) == false)
      {
        image = RenderUtility.scaleImage(image, clipWidth, clipHeight, RenderingHints.VALUE_INTERPOLATION_BICUBIC,
            true);
        content.getContent().setContentCached(image);
        obs = new WaitingImageObserver(image);
        obs.waitImageLoaded();
        scaleTransform = null;
      }
      else
      {
        scaleTransform = AffineTransform.getScaleInstance(scaleX, scaleY);
      }
    }

    while (g2.drawImage(image, scaleTransform, obs) == false)
    {
      obs.waitImageLoaded();
      if (obs.isError())
      {
        LogicalPageDrawable.logger.warn("Error while loading the image during the rendering.");
        break;
      }
    }

    g2.dispose();
    return true;
  }

  protected boolean drawDrawable(final RenderableReplacedContentBox content,
                                 final Graphics2D g2,
                                 final DrawableWrapper d)
  {
    final double x = StrictGeomUtility.toExternalValue(content.getX());
    final double y = StrictGeomUtility.toExternalValue(content.getY());
    final double width = StrictGeomUtility.toExternalValue(content.getWidth());
    final double height = StrictGeomUtility.toExternalValue(content.getHeight());

    if ((width < 0 || height < 0) || (width == 0 && height == 0))
    {
      return false;
    }

    final Graphics2D clone = (Graphics2D) g2.create();


    final StyleSheet styleSheet = content.getStyleSheet();
    final Object attribute = styleSheet.getStyleProperty(ElementStyleKeys.ANTI_ALIASING);
    if (attribute != null)
    {
      if (Boolean.TRUE.equals(attribute))
      {
        clone.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      }
      else if (Boolean.FALSE.equals(attribute))
      {
        clone.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
      }

    }
    if (RenderUtility.isFontSmooth(styleSheet, metaData))
    {
      clone.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    }
    else
    {
      clone.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
    }

    if (strictClipping == false)
    {
      final double extraPadding;
      final Object o = styleSheet.getStyleProperty(ElementStyleKeys.STROKE);
      if (o instanceof BasicStroke)
      {
        final BasicStroke stroke = (BasicStroke) o;
        extraPadding = stroke.getLineWidth() / 2.0;
      }
      else
      {
        extraPadding = 0.5;
      }

      final Rectangle2D.Double clipBounds = new Rectangle2D.Double
          (x - extraPadding, y - extraPadding, width + 2 * extraPadding, height + 2 * extraPadding);

      clone.clip(clipBounds);
      clone.translate(x, y);
    }
    else
    {
      final Rectangle2D.Double clipBounds = new Rectangle2D.Double(x, y, width + 1, height + 1);

      clone.clip(clipBounds);
      clone.translate(x, y);
    }
    configureGraphics(styleSheet, clone);
    configureStroke(styleSheet, clone);
    final Rectangle2D.Double bounds = new Rectangle2D.Double(0, 0, width, height);
    d.draw(clone, bounds);
    clone.dispose();
    return true;
  }

  protected void drawText(final RenderableText renderableText)
  {
    drawText(renderableText, renderableText.getX() + renderableText.getWidth());
  }

  /**
   * Renders the glyphs stored in the text node.
   *
   * @param renderableText the text node that should be rendered.
   * @param contentX2
   */
  protected void drawText(final RenderableText renderableText, final long contentX2)
  {
    if (renderableText.getLength() == 0)
    {
      // This text is empty.
      return;
    }

    final long posX = renderableText.getX();
    final long posY = renderableText.getY();

    final Graphics2D g2;
    if (getTextSpec() == null)
    {
      g2 = (Graphics2D) getGraphics().create();
      final StyleSheet layoutContext = renderableText.getStyleSheet();
      configureGraphics(layoutContext, g2);
      g2.setStroke(LogicalPageDrawable.DEFAULT_STROKE);

      if (RenderUtility.isFontSmooth(layoutContext, metaData))
      {
        g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
      }
      else
      {
        g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
      }
    }
    else
    {
      g2 = getTextSpec().getGraphics();
    }

    // This shifting is necessary to make sure that all text is rendered like in the previous versions.
    // In the earlier versions, we did not really obey to the baselines of the text, we just hoped and prayed.
    // Therefore, all text was printed at the bottom of the text elements. With the introduction of the full
    // font metrics setting, this situation got a little bit better, for the price that text-elements became
    // nearly unpredictable ..
    //
    // The code below may be weird, but at least it is predictable weird.

    final FontMetrics fm = g2.getFontMetrics();
    final Rectangle2D rect = fm.getMaxCharBounds(g2);
    final long awtBaseLine = StrictGeomUtility.toInternalValue(-rect.getY());

    final GlyphList gs = renderableText.getGlyphs();
    if (metaData.isFeatureSupported(OutputProcessorFeature.FAST_FONTRENDERING) &&
        isNormalTextSpacing(renderableText))
    {
      final int maxLength = renderableText.computeMaximumTextSize(contentX2);
      final String text = gs.getText(renderableText.getOffset(), maxLength, codePointBuffer);
      final float y = (float) StrictGeomUtility.toExternalValue(posY + awtBaseLine);
      g2.drawString(text, (float) StrictGeomUtility.toExternalValue(posX), y);
    }
    else
    {
      final ExtendedBaselineInfo baselineInfo = renderableText.getBaselineInfo();
      final int maxPos = renderableText.getOffset() + renderableText.computeMaximumTextSize(contentX2);
      long runningPos = posX;
      final long baseline = baselineInfo.getBaseline(baselineInfo.getDominantBaseline());
      final long baselineDelta = awtBaseLine - baseline;
      final float y = (float) (StrictGeomUtility.toExternalValue
          (posY + awtBaseLine + baselineDelta));
      for (int i = renderableText.getOffset(); i < maxPos; i++)
      {
        final Glyph g = gs.getGlyph(i);
        g2.drawString(gs.getGlyphAsString(i, codePointBuffer),
            (float) StrictGeomUtility.toExternalValue(runningPos), y);
        runningPos += RenderableText.convert(g.getWidth()) + g.getSpacing().getMinimum();
      }
    }
    g2.dispose();
  }

  protected void drawComplexText(final RenderableComplexText renderableComplexText, final Graphics2D g2)
  {
    final long posX = renderableComplexText.getX();
    final long posY = renderableComplexText.getY();

    float baseline = renderableComplexText.getParagraphFontMetrics().getAscent();
    final float y = (float) StrictGeomUtility.toExternalValue(posY) + baseline;

    renderableComplexText.getTextLayout().draw(g2, (float) StrictGeomUtility.toExternalValue(posX), y);

    g2.dispose();
  }

  protected final CodePointBuffer getCodePointBuffer()
  {
    return codePointBuffer;
  }

  protected boolean isNormalTextSpacing(final RenderableText text)
  {
    return text.isNormalTextSpacing();
  }

  protected void configureStroke(final StyleSheet layoutContext, final Graphics2D g2)
  {
    final Stroke styleProperty = (Stroke) layoutContext.getStyleProperty(ElementStyleKeys.STROKE);
    if (styleProperty != null)
    {
      g2.setStroke(styleProperty);
    }
    else
    {
      // Apply a default one ..
      g2.setStroke(LogicalPageDrawable.DEFAULT_STROKE);
    }
  }

  protected void configureGraphics(final StyleSheet layoutContext, final Graphics2D g2)
  {
    final boolean bold = layoutContext.getBooleanStyleProperty(TextStyleKeys.BOLD);
    final boolean italics = layoutContext.getBooleanStyleProperty(TextStyleKeys.ITALIC);

    int style = Font.PLAIN;
    if (bold)
    {
      style |= Font.BOLD;
    }
    if (italics)
    {
      style |= Font.ITALIC;
    }

    final Color cssColor = (Color) layoutContext.getStyleProperty(ElementStyleKeys.PAINT);
    g2.setColor(cssColor);

    final int fontSize = layoutContext.getIntStyleProperty(TextStyleKeys.FONTSIZE,
        (int) metaData.getNumericFeatureValue(OutputProcessorFeature.DEFAULT_FONT_SIZE));

    final String fontName = metaData.getNormalizedFontFamilyName
        ((String) layoutContext.getStyleProperty(TextStyleKeys.FONT));
    g2.setFont(new Font(fontName, style, fontSize));
  }

  public OutputProcessorMetaData getMetaData()
  {
    return metaData;
  }

  public void clip(final StrictBounds bounds)
  {
    final Graphics2D g = getGraphics();
    graphicsContexts.push(g);

    graphics = (Graphics2D) g.create();
    graphics.clip(StrictGeomUtility.createAWTRectangle(bounds));
  }

  public void clearClipping()
  {
    graphics.dispose();
    graphics = graphicsContexts.pop();
  }

  public Graphics2D getGraphics()
  {
    return graphics;
  }

  /**
   * Retries the nodes under the given coordinate which have a given attribute set. If name and namespace are null, all
   * nodes are returned. The nodes returned are listed in their respective hierarchical order.
   *
   * @param x         the x coordinate
   * @param y         the y coordinate
   * @param namespace the namespace on which to filter on
   * @param name      the name on which to filter on
   * @return the ordered list of nodes.
   */
  public RenderNode[] getNodesAt(final double x, final double y, final String namespace, final String name)
  {
    return collectSelectedNodesStep.getNodesAt
        (this.rootBox, StrictGeomUtility.createBounds(x, y, 1, 1), namespace, name);
  }

  public RenderNode[] getNodesAt(final double x, final double y,
                                 final double width, final double height,
                                 final String namespace, final String name)
  {
    return collectSelectedNodesStep.getNodesAt
        (this.rootBox, StrictGeomUtility.createBounds(x, y, width, height), namespace, name);
  }


}
TOP

Related Classes of org.pentaho.reporting.engine.classic.core.modules.output.pageable.graphics.internal.LogicalPageDrawable

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.