/*
*
* * 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) 2006 - 2009 Pentaho Corporation.. All rights reserved.
*
*/
package org.pentaho.reporting.engine.classic.core.layout.process.text;
import java.awt.Image;
import java.awt.font.GraphicAttribute;
import java.awt.font.ImageGraphicAttribute;
import java.awt.font.TextAttribute;
import java.text.AttributedCharacterIterator.Attribute;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.pentaho.reporting.engine.classic.core.ReportAttributeMap;
import org.pentaho.reporting.engine.classic.core.layout.model.LayoutNodeTypes;
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.output.OutputProcessorMetaData;
import org.pentaho.reporting.engine.classic.core.layout.process.util.ReplacedContentUtil;
import org.pentaho.reporting.engine.classic.core.style.StyleSheet;
import org.pentaho.reporting.engine.classic.core.style.TextDirection;
import org.pentaho.reporting.engine.classic.core.style.TextStyleKeys;
import org.pentaho.reporting.engine.classic.core.style.WhitespaceCollapse;
import org.pentaho.reporting.engine.classic.core.util.InstanceID;
import org.pentaho.reporting.libraries.base.util.ArgumentNullException;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
public class RichTextSpecProducer
{
private static class AttributedStringChunk
{
private String text;
private Map<Attribute, Object> attributes;
private ReportAttributeMap<Object> originalAttributes;
private StyleSheet styleSheet;
private InstanceID instanceID;
private RenderNode node;
private AttributedStringChunk(final String text,
final Map<Attribute, Object> attributes,
final ReportAttributeMap<Object> originalAttributes,
final StyleSheet styleSheet, final InstanceID instanceID,
final RenderNode node)
{
ArgumentNullException.validate("text", text);
ArgumentNullException.validate("attributes", attributes);
ArgumentNullException.validate("node", node);
ArgumentNullException.validate("originalAttributes", originalAttributes);
ArgumentNullException.validate("styleSheet", styleSheet);
ArgumentNullException.validate("instanceID", instanceID);
if (text.length() == 0)
{
this.text = "\u0200";
}
else
{
this.text = text;
}
this.instanceID = instanceID;
this.node = node;
this.attributes = attributes;
this.originalAttributes = originalAttributes;
this.styleSheet = styleSheet;
}
public String getText()
{
return text;
}
public Map<Attribute, Object> getAttributes()
{
return attributes;
}
public ReportAttributeMap<Object> getOriginalAttributes()
{
return originalAttributes;
}
public StyleSheet getStyleSheet()
{
return styleSheet;
}
public InstanceID getInstanceID()
{
return instanceID;
}
public RenderNode getNode()
{
return node;
}
}
public static RichTextSpec compute(final RenderBox lineBoxContainer,
final OutputProcessorMetaData metaData,
final ResourceManager resourceManager)
{
return new RichTextSpecProducer(metaData, resourceManager).computeText(lineBoxContainer);
}
private RichTextImageProducer imageProducer;
private OutputProcessorMetaData metaData;
public RichTextSpecProducer(final OutputProcessorMetaData metaData,
final ResourceManager resourceManager)
{
ArgumentNullException.validate("metaData", metaData);
ArgumentNullException.validate("resourceManager", resourceManager);
this.metaData = metaData;
imageProducer = new RichTextImageProducer(metaData, resourceManager);
}
private RichTextSpec computeText(final RenderBox lineBoxContainer)
{
List<AttributedStringChunk> attr = new ArrayList<AttributedStringChunk>();
computeText(lineBoxContainer, attr);
if (attr.isEmpty())
{
attr.add(new AttributedStringChunk("", computeStyle(lineBoxContainer.getStyleSheet()),
lineBoxContainer.getAttributes(), lineBoxContainer.getStyleSheet(), new InstanceID(), lineBoxContainer));
}
attr = processWhitespaceRules(lineBoxContainer, attr);
StringBuilder text = new StringBuilder();
for (final AttributedStringChunk chunk : attr)
{
text.append(chunk.getText());
}
TextDirection direction = (TextDirection)
lineBoxContainer.getStyleSheet().getStyleProperty(TextStyleKeys.DIRECTION, TextDirection.LTR);
return new RichTextSpec(text.toString(), direction, convertNodes(attr));
}
public RichTextSpec computeText(final RenderableComplexText textNode,
final String textChunk)
{
List<AttributedStringChunk> attr = new ArrayList<AttributedStringChunk>();
attr.add(new AttributedStringChunk(textChunk,
computeStyle(textNode.getStyleSheet()), textNode.getAttributes(),
textNode.getStyleSheet(), textNode.getInstanceId(), textNode));
StringBuilder text = new StringBuilder();
for (final AttributedStringChunk chunk : attr)
{
text.append(chunk.getText());
}
TextDirection direction = (TextDirection)
textNode.getStyleSheet().getStyleProperty(TextStyleKeys.DIRECTION, TextDirection.LTR);
return new RichTextSpec(text.toString(), direction, convertNodes(attr));
}
private List<RichTextSpec.StyledChunk> convertNodes(final List<AttributedStringChunk> chunks)
{
ArrayList<RichTextSpec.StyledChunk> result = new ArrayList<RichTextSpec.StyledChunk>(chunks.size());
int startPosition = 0;
for (final AttributedStringChunk chunk : chunks)
{
int length = chunk.getText().length();
int endIndex = startPosition + length;
result.add(new RichTextSpec.StyledChunk(startPosition, endIndex, chunk.getNode(), chunk.getAttributes(),
chunk.getOriginalAttributes(), chunk.getStyleSheet(), chunk.getInstanceID(), chunk.getText()));
startPosition = endIndex;
}
return result;
}
private List<AttributedStringChunk> processWhitespaceRules(final RenderBox lineBoxContainer,
final List<AttributedStringChunk> attrs)
{
// todo
Object ws = lineBoxContainer.getStyleSheet().getStyleProperty(TextStyleKeys.WHITE_SPACE_COLLAPSE);
if (WhitespaceCollapse.PRESERVE_BREAKS.equals(ws))
{
// linebreaks disabled
}
else if (WhitespaceCollapse.COLLAPSE.equals(ws))
{
// normal linebreaks, but duplicate spaces removed
}
else if (WhitespaceCollapse.DISCARD.equals(ws))
{
// all whitespaces removed
}
return attrs;
}
private void computeText(final RenderBox box, final List<AttributedStringChunk> chunks)
{
RenderNode node = box.getFirstChild();
while (node != null)
{
if (node.getNodeType() == LayoutNodeTypes.TYPE_NODE_COMPLEX_TEXT)
{
final RenderableComplexText complexNode = (RenderableComplexText) node;
chunks.add(new AttributedStringChunk(complexNode.getRawText(), computeStyle(node.getStyleSheet()),
node.getAttributes(), node.getStyleSheet(), node.getInstanceId(), node));
}
else if (node.getNodeType() == LayoutNodeTypes.TYPE_BOX_CONTENT)
{
final RenderableReplacedContentBox contentBox = (RenderableReplacedContentBox) node;
final long width = ReplacedContentUtil.computeWidth(contentBox);
final long height = ReplacedContentUtil.computeHeight(contentBox, 0, width);
contentBox.setCachedWidth(width);
contentBox.setCachedHeight(height);
contentBox.setWidth(width);
contentBox.setHeight(height);
chunks.add(new AttributedStringChunk("@", computeImageStyle(node.getStyleSheet(), contentBox),
node.getAttributes(), node.getStyleSheet(), node.getInstanceId(), node));
}
else if (node instanceof RenderBox)
{
computeText((RenderBox) node, chunks);
}
node = node.getNext();
}
}
private Map<Attribute, Object> computeImageStyle(final StyleSheet layoutContext,
final RenderableReplacedContentBox content)
{
final Image image = imageProducer.createImagePlaceholder(content);
ImageGraphicAttribute iga =
new ImageGraphicAttribute(image, GraphicAttribute.BOTTOM_ALIGNMENT);
Map<Attribute, Object> attrs = computeStyle(layoutContext);
attrs.put(TextAttribute.CHAR_REPLACEMENT, iga);
return attrs;
}
private Map<Attribute, Object> computeStyle(final StyleSheet layoutContext)
{
Map<Attribute, Object> result = new HashMap<Attribute, Object>();
// Determine font style
if (layoutContext.getBooleanStyleProperty(TextStyleKeys.ITALIC))
{
result.put(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
}
else
{
result.put(TextAttribute.POSTURE, TextAttribute.POSTURE_REGULAR);
}
if (layoutContext.getBooleanStyleProperty(TextStyleKeys.BOLD))
{
result.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
}
else
{
result.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_REGULAR);
}
final String fontNameRaw = (String) layoutContext.getStyleProperty(TextStyleKeys.FONT);
final String fontName = metaData.getNormalizedFontFamilyName(fontNameRaw);
result.put(TextAttribute.FAMILY, fontName);
result.put(TextAttribute.SIZE, layoutContext.getIntStyleProperty(TextStyleKeys.FONTSIZE, 12));
if (layoutContext.getBooleanStyleProperty(TextStyleKeys.UNDERLINED))
{
result.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
}
if (layoutContext.getBooleanStyleProperty(TextStyleKeys.STRIKETHROUGH))
{
result.put(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
}
// character spacing
result.put(TextAttribute.TRACKING, layoutContext.getDoubleStyleProperty(TextStyleKeys.X_MIN_LETTER_SPACING, 0) / 10);
return result;
}
}