/* * JasperReports - Free Java Reporting Library. * Copyright (C) 2001 - 2009 Jaspersoft Corporation. All rights reserved. * http://www.jaspersoft.com * * Unless you have purchased a commercial license agreement from Jaspersoft, * the following license terms apply: * * This program is part of JasperReports. * * JasperReports is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * JasperReports 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. * * You should have received a copy of the GNU Lesser General Public License * along with JasperReports. If not, see <http://www.gnu.org/licenses/>. */ package net.sf.jasperreports.engine.util; import java.awt.Font; import java.awt.font.TextAttribute; import java.awt.geom.AffineTransform; import java.text.AttributedCharacterIterator; import java.text.AttributedString; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import net.sf.jasperreports.engine.JRRuntimeException; /** * @author Teodor Danciu (teodord@users.sourceforge.net) * @version $Id: JRStyledText.java 3717 2010-04-09 10:01:33Z teodord $ */ public class JRStyledText implements Cloneable { /** * */ public static final String PROPERTY_AWT_IGNORE_MISSING_FONT = JRProperties.PROPERTY_PREFIX + "awt.ignore.missing.font"; private static final String PROPERTY_AWT_SUPERSCRIPT_FIX_ENABLED = JRProperties.PROPERTY_PREFIX + "awt.superscript.fix.enabled"; private static final boolean AWT_SUPERSCRIPT_FIX_ENABLED = System.getProperty("java.version").startsWith("1.6") && JRProperties.getBooleanProperty(PROPERTY_AWT_SUPERSCRIPT_FIX_ENABLED); private static final Set FONT_ATTRS = new HashSet(); static { FONT_ATTRS.add(TextAttribute.FAMILY); FONT_ATTRS.add(TextAttribute.WEIGHT); FONT_ATTRS.add(TextAttribute.POSTURE); FONT_ATTRS.add(TextAttribute.SIZE); FONT_ATTRS.add(TextAttribute.SUPERSCRIPT); } /** * */ private StringBuffer sbuffer = new StringBuffer(); private List runs = new ArrayList(); private AttributedString attributedString = null; private AttributedString awtAttributedString = null; private Map globalAttributes; private Locale locale; /** * */ public JRStyledText() { this(null); } /** * */ public JRStyledText(Locale locale) { this.locale = locale; } /** * */ public void append(String text) { sbuffer.append(text); attributedString = null; awtAttributedString = null; } /** * */ public void addRun(Run run) { runs.add(run); attributedString = null; awtAttributedString = null; } /** * */ public int length() { return sbuffer.length(); } /** * */ public String getText() { return sbuffer.toString(); } /** * */ public Locale getLocale() { return locale; } /** * */ public AttributedString getAttributedString() { if (attributedString == null) { attributedString = new AttributedString(sbuffer.toString()); for(int i = runs.size() - 1; i >= 0; i--) { Run run = (Run)runs.get(i); if (run.startIndex != run.endIndex && run.attributes != null) { attributedString.addAttributes(run.attributes, run.startIndex, run.endIndex); } } } return attributedString; } /** * Returns an attributed string that only contains standard Java text * attributes; JasperReports specific text attributes of the styled text * are ignored. * * @return an attributed string that only contains standard Java text * attributes */ public AttributedString getAwtAttributedString(boolean ignoreMissingFont) { if (awtAttributedString == null) { awtAttributedString = new AttributedString(sbuffer.toString()); for(int i = runs.size() - 1; i >= 0; i--) { Run run = (Run)runs.get(i); if ( run.startIndex != run.endIndex && run.attributes != null && !run.attributes.isEmpty() ) { for (Iterator it = run.attributes.entrySet().iterator(); it.hasNext();) { Map.Entry entry = (Map.Entry) it.next(); AttributedCharacterIterator.Attribute attribute = (AttributedCharacterIterator.Attribute) entry.getKey(); if (!(attribute instanceof JRTextAttribute)) { Object value = entry.getValue(); awtAttributedString.addAttribute(attribute, value, run.startIndex, run.endIndex); } } } } AttributedCharacterIterator iterator = awtAttributedString.getIterator(); int runLimit = 0; AffineTransform atrans = null; while(runLimit < iterator.getEndIndex() && (runLimit = iterator.getRunLimit(FONT_ATTRS)) <= iterator.getEndIndex()) { Map attrs = iterator.getAttributes(); String familyName = (String)attrs.get(TextAttribute.FAMILY); Font awtFont = JRFontUtil.getAwtFontFromBundles( familyName, ((TextAttribute.WEIGHT_BOLD.equals(attrs.get(TextAttribute.WEIGHT))?Font.BOLD:Font.PLAIN) |(TextAttribute.POSTURE_OBLIQUE.equals(attrs.get(TextAttribute.POSTURE))?Font.ITALIC:Font.PLAIN)), ((Float)attrs.get(TextAttribute.SIZE)).intValue(), locale, ignoreMissingFont ); if (awtFont == null) { // The font was not found in any of the font extensions, so it is expected that the TextAttribute.FAMILY attribute // will be used by AWT. In that case, we want make sure the font family name is available to the JVM. JRFontUtil.checkAwtFont(familyName, ignoreMissingFont); } else { if (AWT_SUPERSCRIPT_FIX_ENABLED && atrans != null) { double y = atrans.getTranslateY(); atrans = new AffineTransform(); atrans.translate(0, - y); awtFont = awtFont.deriveFont(atrans); atrans = null; } Integer superscript = (Integer)attrs.get(TextAttribute.SUPERSCRIPT); if (TextAttribute.SUPERSCRIPT_SUPER.equals(superscript)) { atrans = new AffineTransform(); atrans.scale(2 / 3d, 2 / 3d); atrans.translate(0, - awtFont.getSize() / 2f); awtFont = awtFont.deriveFont(atrans); } else if (TextAttribute.SUPERSCRIPT_SUB.equals(superscript)) { atrans = new AffineTransform(); atrans.scale(2 / 3d, 2 / 3d); atrans.translate(0, awtFont.getSize() / 2f); awtFont = awtFont.deriveFont(atrans); } awtAttributedString.addAttribute(TextAttribute.FONT, awtFont, iterator.getIndex(), runLimit); } iterator.setIndex(runLimit); } } return awtAttributedString; } /** * */ public List getRuns() { return runs; } /** * */ public static class Run implements Cloneable { /** * */ public Map attributes = null; public int startIndex = 0; public int endIndex = 0; /** * */ public Run(Map attributes, int startIndex, int endIndex) { this.attributes = attributes; this.startIndex = startIndex; this.endIndex = endIndex; } protected Object clone() { return cloneRun(); } /** * Clones this object. * * @return a clone of this object */ public Run cloneRun() { try { Run clone = (Run) super.clone(); clone.attributes = cloneAttributesMap(attributes); return clone; } catch (CloneNotSupportedException e) { // never throw new JRRuntimeException(e); } } } public void setGlobalAttributes(Map attributes) { this.globalAttributes = attributes; addRun(new Run(attributes, 0, length())); } public Map getGlobalAttributes() { return globalAttributes; } protected Object clone() throws CloneNotSupportedException { // TODO Auto-generated method stub return super.clone(); } protected static Map cloneAttributesMap(Map attributes) { return attributes == null ? null : new HashMap(attributes); } /** * Clones this object. * * @return a clone of this object */ public JRStyledText cloneText() { try { JRStyledText clone = (JRStyledText) super.clone(); clone.globalAttributes = cloneAttributesMap(globalAttributes); clone.runs = new ArrayList(runs.size()); for (Iterator it = runs.iterator(); it.hasNext();) { Run run = (Run) it.next(); Run runClone = run.cloneRun(); clone.runs.add(runClone); } return clone; } catch (CloneNotSupportedException e) { // never throw new JRRuntimeException(e); } } /** * Inserts a string at specified positions in the styled text. * * <p> * The string is inserted in the style runs located at the insertion * positions. If a style run finished right before the insertion position, * the string will be part of this run (but not of the runs that start * right after the insertion position). * * @param str the string to insert * @param offsets the incremental offsets of the positions at which to * insert the string */ public void insert(String str, short[] offsets) { int insertLength = str.length(); //new buffer to do the insertion StringBuffer newText = new StringBuffer(sbuffer.length() + insertLength * offsets.length); //NOPMD char[] buffer = null; int offset = 0; for (int i = 0; i < offsets.length; i++) { int charCount = offsets[i]; int prevOffset = offset; offset += offsets[i]; //append chunk of text if (buffer == null || buffer.length < charCount) { buffer = new char[charCount]; } sbuffer.getChars(prevOffset, offset, buffer, 0); newText.append(buffer, 0, charCount); //append inserted text newText.append(str); //adjust runs //TODO optimize this? for (Iterator it = runs.iterator(); it.hasNext();) { Run run = (Run) it.next(); if (run.startIndex >= offset) { //inserted before run run.startIndex += insertLength; run.endIndex += insertLength; } else if (run.endIndex >= offset) { //inserted in the middle or immediately after a run //the inserted text is included in the run run.endIndex += insertLength; } } } //append remaining text int charCount = sbuffer.length() - offset; if (buffer == null || buffer.length < charCount) { buffer = new char[charCount]; } sbuffer.getChars(offset, sbuffer.length(), buffer, 0); newText.append(buffer, 0, charCount); //overwrite with the inserted buffer sbuffer = newText; attributedString = null; awtAttributedString = null; } }