/*
* 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 - 2017 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 ) {
final Shape clip = this.graphics.getClip();
boolean watermarkOnTop = getMetaData().isFeatureSupported( OutputProcessorFeature.WATERMARK_PRINTED_ON_TOP );
if ( !watermarkOnTop ) {
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() );
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 );
this.graphics.setClip( clip );
if ( watermarkOnTop ) {
startProcessing( rootBox.getWatermarkArea() );
this.graphics.setClip( clip );
}
}
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 ) {
TextSpec textSpec = getTextSpec();
if ( textSpec != null ) {
textSpec.close();
setTextSpec( null );
}
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 );
}
}
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();
if ( lineBox != null ) {
final boolean needClipping = lineBox.getHeight() > box.getHeight() && !box.isBoxOverflowX();
if ( needClipping ) {
// clip
StrictBounds safeBounds =
new StrictBounds( lineBox.getX(), lineBox.getY(), lineBox.getWidth() * 3 / 2, lineBox.getHeight() );
clip( safeBounds );
}
while ( lineBox != null ) {
processTextLine( lineBox, contentAreaX1, contentAreaX2 );
lineBox = (RenderBox) lineBox.getNext();
}
if ( needClipping ) {
clearClipping();
}
}
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 ) {
// 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 ) {
// 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 );
}
}