/* * 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.table.rtf.helper; import java.awt.Color; import java.io.IOException; import org.pentaho.reporting.engine.classic.core.ImageContainer; import org.pentaho.reporting.engine.classic.core.InvalidReportStateException; 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.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.RenderableReplacedContent; 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.output.RenderUtility; import org.pentaho.reporting.engine.classic.core.layout.process.text.RichTextSpec; import org.pentaho.reporting.engine.classic.core.modules.output.table.base.DefaultTextExtractor; import org.pentaho.reporting.engine.classic.core.style.ElementStyleKeys; import org.pentaho.reporting.engine.classic.core.style.StyleSheet; import org.pentaho.reporting.engine.classic.core.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.fonts.itext.BaseFontFontMetrics; import org.pentaho.reporting.libraries.resourceloader.factory.drawable.DrawableWrapper; import com.lowagie.text.Chunk; import com.lowagie.text.DocumentException; import com.lowagie.text.Element; import com.lowagie.text.Font; import com.lowagie.text.Image; import com.lowagie.text.Paragraph; import com.lowagie.text.TextElementArray; import com.lowagie.text.pdf.BaseFont; /** * Todo: On Block-Level elements, apply the block-level styles like text-alignment and vertical-alignment. * * @author Thomas Morgner */ public class RTFTextExtractor extends DefaultTextExtractor { private static class StyleContext { private TextElementArray target; private RTFOutputProcessorMetaData metaData; private String fontName; private double fontSize; private boolean bold; private boolean italic; private boolean underline; private boolean strikethrough; private Color textColor; private Color backgroundColor; protected StyleContext( final TextElementArray target, final StyleSheet styleSheet, final RTFOutputProcessorMetaData metaData ) { this.target = target; this.metaData = metaData; this.fontName = (String) styleSheet.getStyleProperty( TextStyleKeys.FONT ); this.fontSize = styleSheet.getDoubleStyleProperty( TextStyleKeys.FONTSIZE, 0 ); this.bold = styleSheet.getBooleanStyleProperty( TextStyleKeys.BOLD ); this.italic = styleSheet.getBooleanStyleProperty( TextStyleKeys.ITALIC ); this.underline = styleSheet.getBooleanStyleProperty( TextStyleKeys.UNDERLINED ); this.strikethrough = styleSheet.getBooleanStyleProperty( TextStyleKeys.STRIKETHROUGH ); this.textColor = (Color) styleSheet.getStyleProperty( ElementStyleKeys.PAINT ); this.backgroundColor = (Color) styleSheet.getStyleProperty( ElementStyleKeys.BACKGROUND_COLOR ); } public TextElementArray getTarget() { return target; } public String getFontName() { return fontName; } public double getFontSize() { return fontSize; } public boolean isBold() { return bold; } public boolean isItalic() { return italic; } public boolean isUnderline() { return underline; } public boolean isStrikethrough() { return strikethrough; } public Color getTextColor() { return textColor; } public Color getBackgroundColor() { return backgroundColor; } public void add( final Element element ) { target.add( element ); } public boolean equals( final Object o ) { if ( this == o ) { return true; } if ( o == null || getClass() != o.getClass() ) { return false; } final StyleContext that = (StyleContext) o; if ( bold != that.bold ) { return false; } if ( that.fontSize != fontSize ) { return false; } if ( italic != that.italic ) { return false; } if ( strikethrough != that.strikethrough ) { return false; } if ( underline != that.underline ) { return false; } if ( backgroundColor != null ? !backgroundColor.equals( that.backgroundColor ) : that.backgroundColor != null ) { return false; } if ( fontName != null ? !fontName.equals( that.fontName ) : that.fontName != null ) { return false; } if ( textColor != null ? !textColor.equals( that.textColor ) : that.textColor != null ) { return false; } return true; } public int hashCode() { int result = ( fontName != null ? fontName.hashCode() : 0 ); final long temp = fontSize != +0.0d ? Double.doubleToLongBits( fontSize ) : 0L; result = 29 * result + (int) ( temp ^ ( temp >>> 32 ) ); result = 29 * result + ( bold ? 1 : 0 ); result = 29 * result + ( italic ? 1 : 0 ); result = 29 * result + ( underline ? 1 : 0 ); result = 29 * result + ( strikethrough ? 1 : 0 ); result = 29 * result + ( textColor != null ? textColor.hashCode() : 0 ); result = 29 * result + ( backgroundColor != null ? backgroundColor.hashCode() : 0 ); return result; } public void add( final String text ) { int style = Font.NORMAL; if ( bold ) { style |= Font.BOLD; } if ( italic ) { style |= Font.ITALIC; } if ( strikethrough ) { style |= Font.STRIKETHRU; } if ( underline ) { style |= Font.UNDERLINE; } final BaseFontFontMetrics fontMetrics = metaData.getBaseFontFontMetrics( fontName, fontSize, bold, italic, "utf-8", false, false ); final BaseFont baseFont = fontMetrics.getBaseFont(); final Font font = new Font( baseFont, (float) fontSize, style, textColor ); final Chunk c = new Chunk( text, font ); if ( backgroundColor != null ) { c.setBackground( backgroundColor ); } target.add( c ); } } private RTFImageCache imageCache; private FastStack<StyleContext> context; private RTFOutputProcessorMetaData metaData; private boolean handleImages; public RTFTextExtractor( final RTFOutputProcessorMetaData metaData ) { super( metaData ); this.metaData = metaData; this.handleImages = metaData.isFeatureSupported( RTFOutputProcessorMetaData.IMAGES_ENABLED ); context = new FastStack<StyleContext>( 50 ); } private StyleContext getCurrentContext() { return context.peek(); } public void compute( final RenderBox box, final TextElementArray cell, final RTFImageCache imageCache ) { this.context.clear(); this.context.push( new StyleContext( cell, box.getStyleSheet(), metaData ) ); this.imageCache = imageCache; super.compute( box ); } protected boolean startInlineBox( final InlineRenderBox box ) { if ( box.getStaticBoxLayoutProperties().isVisible() == false ) { return false; } // Compare the text style .. final StyleContext currentContext = getCurrentContext(); final StyleContext boxContext = new StyleContext( currentContext.getTarget(), box.getStyleSheet(), metaData ); if ( currentContext.equals( boxContext ) == false ) { if ( getTextLength() > 0 ) { final String text = getText(); currentContext.add( text ); clearText(); } this.context.pop(); this.context.push( boxContext ); } return true; } protected void finishInlineBox( final InlineRenderBox box ) { final StyleContext currentContext = getCurrentContext(); if ( getTextLength() > 0 ) { final String text = getText(); currentContext.add( text ); clearText(); } } protected void processOtherNode( final RenderNode node ) { final StrictBounds paragraphBounds = getParagraphBounds(); if ( isTextLineOverflow() && node.isNodeVisible( paragraphBounds, isOverflowX(), isOverflowY() ) == false ) { return; } super.processOtherNode( node ); if ( node.getNodeType() == LayoutNodeTypes.TYPE_NODE_TEXT ) { if ( node.isVirtualNode() ) { return; } if ( ( node.getX() + node.getWidth() ) > ( paragraphBounds.getX() + paragraphBounds.getWidth() ) ) { // This node will only be partially visible. The end-of-line marker will not apply. return; } final RenderableText text = (RenderableText) node; if ( text.isForceLinebreak() ) { final StyleContext currentContext = getCurrentContext(); if ( getTextLength() > 0 ) { currentContext.add( getText() ); clearText(); } context.pop(); final StyleContext cellContext = getCurrentContext(); cellContext.add( currentContext.getTarget() ); context.push( new StyleContext( new Paragraph(), text.getStyleSheet(), metaData ) ); } } else if ( node.getNodeType() == LayoutNodeTypes.TYPE_NODE_COMPLEX_TEXT ) { // todo: check if special text processing is required for RenderableComplexText nodes // return; if ( node.isVirtualNode() ) { return; } if ( ( node.getX() + node.getWidth() ) > ( paragraphBounds.getX() + paragraphBounds.getWidth() ) ) { // This node will only be partially visible. The end-of-line marker will not apply. return; } final RenderableComplexText text = (RenderableComplexText) node; if ( text.isForceLinebreak() ) { final StyleContext currentContext = getCurrentContext(); if ( getTextLength() > 0 ) { currentContext.add( getText() ); clearText(); } context.pop(); final StyleContext cellContext = getCurrentContext(); cellContext.add( currentContext.getTarget() ); context.push( new StyleContext( new Paragraph(), text.getStyleSheet(), metaData ) ); } } } protected void processRenderableContent( final RenderableReplacedContentBox node ) { float targetWidth = (float) StrictGeomUtility.toExternalValue( node.getWidth() ); float targetHeight = (float) StrictGeomUtility.toExternalValue( node.getHeight() ); try { final RenderableReplacedContent rpc = node.getContent(); final Object rawObject = rpc.getRawObject(); if ( rawObject instanceof ImageContainer ) { final Image image = imageCache.getImage( (ImageContainer) rawObject ); if ( image == null ) { return; } final StyleContext currentContext = getCurrentContext(); if ( getTextLength() > 0 ) { currentContext.add( getText() ); clearText(); } image.scaleToFit( targetWidth, targetHeight ); currentContext.add( image ); } else if ( rawObject instanceof DrawableWrapper ) { final StrictBounds rect = new StrictBounds( node.getX(), node.getY(), node.getWidth(), node.getHeight() ); final ImageContainer ic = RenderUtility.createImageFromDrawable( (DrawableWrapper) rawObject, rect, node, metaData ); if ( ic == null ) { return; } final Image image = imageCache.getImage( ic ); if ( image == null ) { return; } final StyleContext currentContext = getCurrentContext(); if ( getTextLength() > 0 ) { currentContext.add( getText() ); clearText(); } image.scaleToFit( targetWidth, targetHeight ); currentContext.add( image ); } } catch ( DocumentException ioe ) { throw new InvalidReportStateException( "Failed to extract text", ioe ); } catch ( IOException e ) { // double ignore .. throw new InvalidReportStateException( "Failed to extract text", e ); } } protected void processParagraphChilds( final ParagraphRenderBox box ) { context.push( new StyleContext( new Paragraph(), box.getStyleSheet(), metaData ) ); clearText(); super.processParagraphChilds( box ); final StyleContext currentContext = getCurrentContext(); if ( getTextLength() > 0 ) { currentContext.add( getText() ); clearText(); } context.pop(); getCurrentContext().add( currentContext.getTarget() ); } protected void drawComplexText( final RenderableComplexText renderableComplexText ) { if ( renderableComplexText.getRawText().length() == 0 ) { // This text is empty. return; } if ( renderableComplexText.isNodeVisible( getParagraphBounds(), isOverflowX(), isOverflowY() ) == false ) { return; } // check if we have to process inline text elements if ( renderableComplexText.getRichText().getStyleChunks().size() > 1 ) { // iterate through all inline elements for ( final RichTextSpec.StyledChunk styledChunk : renderableComplexText.getRichText().getStyleChunks() ) { // Add style for current styled chunk final StyleContext boxContext = new StyleContext( getCurrentContext().getTarget(), styledChunk.getStyleSheet(), metaData ); if ( styledChunk.getText().length() > 0 ) { final String text = styledChunk.getText(); boxContext.add( text ); clearText(); } context.pop(); context.push( boxContext ); } } else { super.drawComplexText( renderableComplexText ); } } }