/*
* 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 - 2009 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 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;
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.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.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.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.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.libraries.base.util.FastStack;
import org.pentaho.reporting.libraries.fonts.itext.BaseFontFontMetrics;
import org.pentaho.reporting.libraries.resourceloader.factory.drawable.DrawableWrapper;
/**
* 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 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(50);
}
private StyleContext getCurrentContext()
{
return (StyleContext) 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));
}
}
}
protected void processRenderableContent(final RenderableReplacedContentBox node)
{
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();
}
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();
}
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());
}
}