/* * * * 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.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; } }