All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.myfaces.trinidadinternal.image.painter.TextPainter Maven / Gradle / Ivy

The newest version!
/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package org.apache.myfaces.trinidadinternal.image.painter;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;

import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.font.TextAttribute;
import java.awt.font.TextHitInfo;
import java.awt.font.TextLayout;
import java.text.AttributedString;

import org.apache.myfaces.trinidadinternal.util.nls.LocaleUtils;


/**
 * A painter capable of painting a string using the alignment of its
 * PaintContext.  If the string contains newlines, multiple lines
 * of text will be painted.  

* * This class uses new APIs in Java2 and propertly works with * BufferedImages.

* * This TextPainter should be used for all String objects used in the * ImageGenerator.

*

* @version $Name: $ ($Revision: adfrt/faces/adf-faces-impl/src/main/java/oracle/adfinternal/view/faces/image/painter/TextPainter.java#1 $) $Date: 11-nov-2005.14:59:38 $ */ public class TextPainter extends AbstractPainter { /** * Creates a TextPainter object, using the default label data key. * and supporting mnemonics. */ public TextPainter() { _init(PaintContext.LABEL_KEY, true); } /** * Creates a TextPainter object using the dataKey to get its * data and supporting mnemonics. *

* @param dataKey Key used by this TextPainter to retrieve the String * this TextPainter paints. *

* @see PaintContext#getPaintData */ public TextPainter( Object dataKey ) { _init(dataKey, true); } /** * Creates a TextPainter object. *

* @param dataKey Key used by this TextPainter to retrieve the * String this TextPainter paints. * @param supportMnemonics True if this TextPainter should ask its * Paint context for a mnemonic to paint. *

* @see PaintContext#getPaintData */ public TextPainter( Object dataKey, boolean supportMnemonics ) { _init(dataKey, supportMnemonics); } /** * Returns the preferred size of the painter. *

* @param context Context for determining the preferred size. * @return The preferred size of the Painter. */ @Override public Dimension getPreferredSize( PaintContext context ) { return _getSize(context, true); } /** * Returns the minimum size of the painter. *

* @param context Context for determining the minimum size. * @return The minimum size of the Painter. */ public Dimension getMinimumSize( PaintContext context ) { return _getSize(context, false); } /** * Paints the TextPainter at the given location. *

* @param context Context for painting. * @param g Graphics object to draw into. * @param x X position to draw at. * @param y Y position to draw at. * @param width Width to draw into. * @param height Height to draw into. */ public void paint( PaintContext context, Graphics g, int x, int y, int width, int height ) { // We synchronize on the _FONT_LOCK object so that we only // render/measure one text String at a time. // This oddness is due to bug 1852105 (ANTIALIASED TEXT MAY // SOMETIMES APPEAR BOLD). The problem seems to be due to // buggy behavior in how AWT maintains internal font state. // After rendering/measuring with a bold antialiased font, // subsequent rendering/measuring with the same font results // in bold appearance/metrics, even if the style is set to // Font.PLAIN. To avoid this, we serialize all text // rendering/measuring, and explicitly force the font // to be reset before doing any rendering/measuring. // Note: This behavior has not yet been observed in JDK 1.4, // so hopefully this hack can be removed before long. synchronized (_FONT_LOCK) { // Reset the font state before we do anything. _resetFont(g); // Now it should be safe to get/use the FontMetrics, // as well as to paint the text. FontMetrics metrics = g.getFontMetrics(); if (metrics != null) { String text = getStringData(context); if (text != null) { int textLength = text.length(); if (textLength != 0) { float alignmentX = context.getInteriorAlignmentX(); int numLines = _getNumberOfTextLines(text); // // adjust Y for vertical alignment // float alignmentY = context.getInteriorAlignmentY(); if (alignmentY != Component.TOP_ALIGNMENT) { int stringHeight = numLines * metrics.getHeight(); if (stringHeight < height) { y += (height - stringHeight) * alignmentY; } } // adjust y for baseline of text y += metrics.getAscent(); // // get the mnemonic, if any // int mnemonicIndex = -1; if (_supportMnemonics) { Object mnemonic = context.getPaintData(PaintContext.MNEMONIC_INDEX_KEY); if ((mnemonic != null) && (mnemonic instanceof Integer)) { mnemonicIndex = ((Integer)mnemonic).intValue(); } } // // Draw each line of text. We treat single line of text that // end in a carraige return as multiple lines of text to make // sure that we don't pass a carraige return at the end of // a single line of text to paintText() // if ((numLines == 1) && (text.charAt(textLength - 1) != '\n')) { // all text on the same line paintText(context, g, metrics, text, x, y, width, alignmentX, mnemonicIndex); } else { int lastIndex; int currIndex = -1; int lineHeight = metrics.getHeight(); do { lastIndex = currIndex + 1; if (lastIndex >= textLength) break; currIndex = text.indexOf('\n', lastIndex); if (currIndex == -1) { currIndex = textLength; } paintText(context, g, metrics, text.substring(lastIndex, currIndex), x, y, width, alignmentX, mnemonicIndex - lastIndex); // move down to next line y += lineHeight; } while (true); } } } } } } /** * This method is called for each line of text painted. Given a line of text, * return the line of text to actually draw. Subclasses, that wish to modify * the text displayed should override this method. *

* @param context Context for determining the text to paint. * @param text Text data for this line. * @param metrics FontMetrics to use to paint the text. * @param availableWidth Number of pixels available to paint the text. *

* @return The String to actually paint. */ protected String getPaintText( PaintContext context, String text, FontMetrics metrics, int availableWidth ) { return text; } /** * Typesafe method to return the String to Paint. * Subclasses that do not use data keys to retrieve * their data should override this method, rather than * getData(), as overriding getStringData() * directly is more efficient. *

* @param context PaintContext to use to retrieve the TextPainter's * String data. *

* @return The String to paint. *

* @see #getData */ protected String getStringData( PaintContext context ) { return (String)getData(context); } /** * Returns the String to use for the minimum size calculation. This allows * subclasses to create TextPainters that use different strings for * calculating their minimum sizes than they do for actually displaying * the current value. The default implementation is to use the current * value. */ protected String getMinimumStringData( PaintContext context ) { return getStringData(context); } @Override protected Object getData( PaintContext context ) { Object data = super.getData(context); if (data != null) { return data.toString(); } else { return null; } } @Override protected Object getDataKey() { return _dataKey; } private Dimension _getSize( PaintContext context, boolean usePreferredSize ) { int width = 0; int height = 0; Font font = context.getPaintFont(); Graphics g = context.getPaintGraphics(); if (font != null) { String text = (usePreferredSize ? getStringData(context) : getMinimumStringData(context)); // Due to bug 1852105 (ANTIALIASED TEXT MAY SOMETIMES APPEAR BOLD), // we synchronize on the _FONT_LOCK object to serialize all text // rendering/measuring. See comment in paint() method above for // more details. synchronized (_FONT_LOCK) { // Reset the font state before we do anything. _resetFont(g); // Now it should be safe to get/use the FontMetrics FontMetrics metrics = context.getFontMetrics(font); if ((metrics != null) && (text != null)) { int numLines = _getNumberOfTextLines(text); if (numLines == 1) { text = StringUtils.getDisplayString(text, context); // get the string width width = _getStringWidth(text, metrics, g); } else { width = _getMaxLineWidth(context, text, metrics, g); } height = (numLines * metrics.getHeight()) - metrics.getLeading(); } } } return new Dimension(width, height); } /** * Computer the number of lines of text that the passed in text contains */ private static final int _getNumberOfTextLines( String text ) { int numLines = 0; int currIndex; int foundIndex = -1; int textLength = text.length(); do { currIndex = foundIndex + 1; if (currIndex == textLength) break; foundIndex = text.indexOf('\n', currIndex); numLines++; } while (foundIndex != -1); return numLines; } private static final int _getStringWidth( String text, FontMetrics metrics, Graphics g ) { // =-=AEW Techically, this isn't correct. We really should // be using AttributedString and TextLayout after setting up // the reading direction. However, as long as we're only // ever getting the width of the _entire_ string, we should be OK. return (int)metrics.getStringBounds(text, g).getWidth(); } private static final int _getMaxLineWidth( PaintContext context, String text, FontMetrics metrics, Graphics g ) { int maxWidth = 0; int lastIndex; int currIndex = -1; int textLength = text.length(); do { lastIndex = currIndex + 1; if (lastIndex >= textLength) break; currIndex = text.indexOf('\n', lastIndex); if (currIndex == -1) currIndex = textLength; String subtext = StringUtils.getDisplayString( text.substring(lastIndex, currIndex), context); // get the width of the string int currWidth = _getStringWidth(subtext, metrics, g); if (maxWidth < currWidth) maxWidth = currWidth; } while (true); return maxWidth; } protected int paintText( PaintContext context, Graphics g, FontMetrics metrics, String text, int x, int y, int availableWidth, float alignmentX, int mnemonicIndex ) { // Convert the text to bidi text = StringUtils.getDisplayString(text, context); // get the text to paint String paintText = getPaintText(context, text, metrics, availableWidth); if (alignmentX != Component.LEFT_ALIGNMENT) { int stringWidth = _getStringWidth(paintText, metrics, g); if (stringWidth < availableWidth) { x += (availableWidth - stringWidth) * alignmentX; } } // paint the text. Before drawing the text, make sure // we clue the system in on the default direction of the run - // we do _not_ want to use the direction of the first character // in this text. Graphics2D g2 = (Graphics2D) g; AttributedString as = new AttributedString(paintText); as.addAttribute(TextAttribute.FONT, g2.getFont()); Object direction; if (context.getReadingDirection() == LocaleUtils.DIRECTION_LEFTTORIGHT) direction = TextAttribute.RUN_DIRECTION_LTR; else direction = TextAttribute.RUN_DIRECTION_RTL; as.addAttribute(TextAttribute.RUN_DIRECTION, direction); g2.drawString(as.getIterator(), x, y); // // paint the mnemonic if necessary // if ((mnemonicIndex >= 0) && (mnemonicIndex < paintText.length())) { TextLayout layout = new TextLayout(as.getIterator(), g2.getFontRenderContext()); TextHitInfo leading = TextHitInfo.leading(mnemonicIndex); TextHitInfo trailing = TextHitInfo.trailing(mnemonicIndex); Rectangle r = layout.getVisualHighlightShape(leading, trailing).getBounds(); // draw a horizontal line under the specified character to indicate // that it is the mnemonic. int left = r.x + x; g.drawLine(left + 1, y + 1, left + r.width - 1, y + 1); } return x; } private void _init( Object dataKey, boolean supportMnemonics ) { _dataKey = dataKey; _supportMnemonics = supportMnemonics; } // This method is needed to work around // bug 1852105 (ANTIALIASED TEXT MAY SOMETIMES APPEAR BOLD). // To get AWT to pick up the right Font/FontMetrics, we // first reset the font on the Graphics object to a 1pt // version of the current font, and then get the font // metrics. Then, we reset the Graphics object to use the // actual font that we want, at which point subsequent calls // to Graphics.getFontMetrics()/paintText() work correctly. private static void _resetFont(Graphics g) { Font font = g.getFont(); // We set the font to be the same font family/style as // the current font, but a different size (1pt). g.setFont(new Font(font.getName(), font.getStyle(), 1)); // Then, get the font metrics, this seems to reset the // graphics state. g.getFontMetrics(); // Finally, reset the original font. Now, if we get the // font metrics again or paint some text, the right font/ // font metrics will be used. g.setFont(font); } private boolean _supportMnemonics; private Object _dataKey; // This lock is used to serialize all text rendering/measuring, // which is done to work around bug 1852105 (ANTIALIASED TEXT // MAY SOMETIMES APPEAR BOLD). private static final Object _FONT_LOCK = new Object(); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy