package org.pentaho.reporting.engine.classic.core.layout.model; import org.pentaho.reporting.engine.classic.core.ReportAttributeMap; import org.pentaho.reporting.engine.classic.core.layout.model.context.NodeLayoutProperties; 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.text.ParagraphFontMetrics; import org.pentaho.reporting.engine.classic.core.layout.process.text.RichTextSpec; import org.pentaho.reporting.engine.classic.core.layout.process.text.RichTextSpecProducer; import org.pentaho.reporting.engine.classic.core.metadata.ElementType; 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.InstanceID; import org.pentaho.reporting.engine.classic.core.util.geom.StrictGeomUtility; import org.pentaho.reporting.libraries.resourceloader.ResourceManager; import java.awt.font.FontRenderContext; import java.awt.font.TextLayout; import java.text.AttributedCharacterIterator; import java.text.BreakIterator; public class RenderableComplexText extends RenderNode { private RichTextSpec richText; private int start; private int end; private String text; private TextLayout textLayout; private boolean forceLinebreak; private ParagraphFontMetrics paragraphFontMetrics; public RenderableComplexText( final StyleSheet styleSheet, final InstanceID instanceID, final ElementType elementType, final ReportAttributeMap<Object> attributes, final RichTextSpec text ) { super( new NodeLayoutProperties( styleSheet, attributes, instanceID, elementType ) ); this.text = text.getText(); this.richText = text; this.forceLinebreak = false; this.start = 0; this.end = text.length(); } public RenderableComplexText( final StyleSheet styleSheet, final InstanceID instanceID, final ElementType elementType, final ReportAttributeMap<Object> attributes, final String text ) { super( new NodeLayoutProperties( styleSheet, attributes, instanceID, elementType ) ); this.text = text; this.richText = null; this.forceLinebreak = false; this.start = 0; this.end = text.length(); } public void computeMinimumChunkWidth( final OutputProcessorMetaData data, final ResourceManager resourceManager ) { if ( getMinimumChunkWidth() != 0 ) { return; } if ( data.isFeatureSupported( OutputProcessorFeature.STRICT_COMPATIBILITY ) == false && getStyleSheet().getBooleanStyleProperty( TextStyleKeys.WORDBREAK ) == false ) { return; } long minimumChunkWidth = 0; BreakIterator wordInstance = BreakIterator.getWordInstance(); wordInstance.setText( text ); final boolean antiAliasing = RenderUtility.isFontSmooth( getStyleSheet(), data ); final FontRenderContext fontRenderContext = new FontRenderContext( null, antiAliasing, true ); int start = wordInstance.first(); for ( int end = wordInstance.next(); end != BreakIterator.DONE; start = end, end = wordInstance.next() ) { String word = text.substring( start, end ); AttributedCharacterIterator attributedCharacterIterator = new RichTextSpecProducer( data, resourceManager ).computeText( this, word ) .createAttributedCharacterIterator(); TextLayout t = new TextLayout( attributedCharacterIterator, fontRenderContext ); double width = t.getVisibleAdvance(); final long wordMinChunkWidth = StrictGeomUtility.toInternalValue( width ); minimumChunkWidth = Math.max( minimumChunkWidth, wordMinChunkWidth ); } setMinimumChunkWidth( minimumChunkWidth ); } public int getNodeType() { return LayoutNodeTypes.TYPE_NODE_COMPLEX_TEXT; } public String getRawText() { return text; } public RichTextSpec getRichText() { if ( richText == null ) { // code-smell - we probably should introduce a "ProcessedComplexText" type to distinguish between // raw text and text that has been processed by the CanvasMinor-step. throw new IllegalStateException( "Calling 'getRichText' is only valid after layouting is complete." ); } return richText.substring( start, end ); } public TextLayout getTextLayout() { return textLayout; } public void setTextLayout( final TextLayout textLayout ) { this.textLayout = textLayout; } public void setForceLinebreak( final boolean forceLinebreak ) { this.forceLinebreak = forceLinebreak; } public boolean isForceLinebreak() { return forceLinebreak; } public RenderableComplexText merge( final RenderableComplexText suffix ) { if ( richText != suffix.richText ) { throw new IllegalStateException( "Not from the same source" ); } final RenderableComplexText text = (RenderableComplexText) derive( true ); text.end = suffix.end; text.setMinimumChunkWidth( Math.max( getMinimumChunkWidth(), suffix.getMinimumChunkWidth() ) ); return text; } public boolean isSameSource( final RenderableComplexText suffix ) { if ( richText != suffix.richText ) { return false; } return true; } public void setParagraphFontMetrics( final ParagraphFontMetrics paragraphFontMetrics ) { this.paragraphFontMetrics = paragraphFontMetrics; } public ParagraphFontMetrics getParagraphFontMetrics() { return paragraphFontMetrics; } }