Created a lightweight class for managing text formatted to fit in a

particular dimension (either horizontal or vertical).


git-svn-id: https://samskivert.googlecode.com/svn/trunk@525 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
mdb
2001-12-19 07:02:50 +00:00
parent fa2a7c7105
commit d6dc2e1822
2 changed files with 363 additions and 0 deletions
@@ -0,0 +1,259 @@
//
// $Id: Label.java,v 1.1 2001/12/19 07:02:49 mdb Exp $
package com.samskivert.swing;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.font.TextLayout;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.text.AttributedString;
import java.text.AttributedCharacterIterator;
import java.util.ArrayList;
import java.util.HashMap;
import com.samskivert.Log;
/**
* The label is a multipurpose text display mechanism that can display
* small amounts of text wrapped to fit into a variety of constrained
* spaces. It can be requested to conform to a particular width or height
* and will expand into the other dimension in order to accomodate the
* text at hand. It is not a component, but is intended for use by
* components and other more heavyweight entities.
*/
public class Label
{
/**
* Constructs a blank label.
*/
public Label ()
{
setText("");
}
/**
* Constructs a label with the supplied text.
*/
public Label (String text)
{
setText(text);
}
/**
* Sets the text to be displayed by this label. This should be
* followed eventually by a call to {@link #layout} and ultimately
* {@link #render} to render the text. Simply setting the text does
* not cause any layout to occur.
*/
public void setText (String text)
{
_text = text;
}
/**
* Sets the font to be used by this label. If the font is not set, the
* current font of the graphics context will be used.
*/
public void setFont (Font font)
{
_font = font;
}
/**
* Sets the target width for this label. If possible text will be
* wrapped to fit into this width, but if an unwrappable run of text
* is encountered that is longer than the target width, it will not be
* forcibly broken. Calling this method will annul any previously
* established target height as we must have one degree of freedom in
* which to maneuver.
*/
public void setTargetWidth (int targetWidth)
{
_constraints.width = targetWidth;
_constraints.height = 0;
}
/**
* Sets the target height for this label. A simple algorithm will be
* used to balance the width of the text in order that there are only
* as many lines of text as fit into the target height. If rendering
* the label as a single line of text causes it to be taller than the
* target height, we simply render ourselves anyway. Calling this
* method will annul any previously established target width as we
* must have one degree of freedom in which to maneuver.
*/
public void setTargetHeight (int targetHeight)
{
_constraints.width = 0;
_constraints.height = targetHeight;
}
/**
* Returns our computed dimensions. Only valid after a call to {@link
* #layout}.
*/
public Dimension getSize ()
{
return _size;
}
/**
* Requests that this label lay out its text, obtaining information
* from the supplied graphics context to do so. It is expected that
* the label will be subsequently rendered in the same graphics
* context or at least one that is configured very similarly. If not,
* wackiness may ensue.
*/
public void layout (Graphics2D gfx)
{
Font font = (_font == null) ? gfx.getFont() : _font;
FontRenderContext frc = gfx.getFontRenderContext();
ArrayList layouts = null;
// if we have a target height, do some processing and convert that
// into a target width
if (_constraints.height > 0) {
TextLayout layout = new TextLayout(textIterator(gfx), frc);
Rectangle2D bounds = layout.getBounds();
int lines = (int)(_constraints.height / getHeight(layout));
lines = Math.max(lines, 1);
int targetWidth = (int)(bounds.getWidth() / lines);
// attempt to lay the text out in the specified width,
// incrementing by 10% each time; limit our attempts to 10
// expansions to avoid infinite loops if something is fucked
for (int i = 0; i < 10; i++) {
LineBreakMeasurer measurer =
new LineBreakMeasurer(textIterator(gfx), frc);
layouts = computeLines(measurer, targetWidth, _size);
if (layouts.size() <= lines) {
break;
}
targetWidth = (int)(targetWidth * 1.1);
}
} else if (_constraints.width > 0) {
LineBreakMeasurer measurer =
new LineBreakMeasurer(textIterator(gfx), frc);
layouts = computeLines(measurer, _constraints.width, _size);
} else {
// we have no target width, simply lay the text out in one big
// fat line and call ourselves good
TextLayout layout = new TextLayout(textIterator(gfx), frc);
Rectangle2D bounds = layout.getBounds();
// for some reason JDK1.3 on Linux chokes on setSize(double,double)
_size.setSize((int)bounds.getWidth(), (int)getHeight(layout));
layouts = new ArrayList();
layouts.add(layout);
}
// create our layouts array
_layouts = new TextLayout[layouts.size()];
layouts.toArray(_layouts);
}
/**
* Computes the lines of text for this label given the specified
* target width. The overall size of the computed lines is stored into
* the size parameter.
*/
protected ArrayList computeLines (
LineBreakMeasurer measurer, int targetWidth, Dimension size)
{
// start with a size of zero
double width = 0, height = 0;
// obtain our new dimensions by using a line break iterator to lay
// out our text one line at a time
ArrayList layouts = new ArrayList();
TextLayout layout;
while ((layout = measurer.nextLayout(targetWidth)) != null) {
Rectangle2D bounds = layout.getBounds();
width = Math.max(width, bounds.getWidth());
height += getHeight(layout);
layouts.add(layout);
}
// fill in the computed size; for some reason JDK1.3 on Linux
// chokes on setSize(double,double)
size.setSize((int)width, (int)height);
return layouts;
}
/**
* Renders the layout at the specified position in the supplied
* graphics context.
*/
public void render (Graphics2D gfx, float x, float y)
{
// nothing to do if we haven't been laid out
if (_layouts == null) {
Log.warning("Label requested to render prior to a call " +
"to layout() [text=" + _text + "].");
return;
}
// render our text
for (int i = 0; i < _layouts.length; i++) {
TextLayout layout = _layouts[i];
y += layout.getAscent();
float dx = layout.isLeftToRight() ? 0 :
_size.width - layout.getAdvance();
layout.draw(gfx, x + dx, y);
y += layout.getDescent() + layout.getLeading();
}
}
/**
* Constructsn an attributed character iterator with our text and the
* appropriate font.
*/
protected AttributedCharacterIterator textIterator (Graphics2D gfx)
{
Font font = (_font == null) ? gfx.getFont() : _font;
HashMap map = new HashMap();
map.put(TextAttribute.FONT, font);
AttributedString text = new AttributedString(_text, map);
return text.getIterator();
}
/**
* Computes the height based on the leading, ascent and descent rather
* than what the layout reports via <code>getBounds()</code> which
* rarely seems to have any bearing on reality.
*/
protected static float getHeight (TextLayout layout)
{
return layout.getLeading() + layout.getAscent() + layout.getDescent();
}
/** The text of the label. */
protected String _text;
protected int _justification;
/** Our size constraints in either the x or y direction. */
protected Dimension _constraints = new Dimension();
/** Our calculated size. */
protected Dimension _size = new Dimension();
/** The font we use when laying out and rendering out text, or null if
* we're to use the default font. */
protected Font _font;
/** Formatted text layout instances that contain each line of text. */
protected TextLayout[] _layouts;
}
@@ -0,0 +1,104 @@
//
// $Id: LabelDemo.java,v 1.1 2001/12/19 07:02:50 mdb Exp $
package com.samskivert.swing;
import java.awt.*;
import javax.swing.*;
public class LabelDemo extends JPanel
{
public LabelDemo ()
{
// create our labels
String text = "The quick brown fox jumped over the lazy dog. " +
"He then popped into the butcher's and picked up some mutton.";
Font font = new Font("Courier", Font.PLAIN, 10);
_labelZero = new Label(text);
_labelZero.setFont(font);
_labelOne = new Label(text);
_labelOne.setFont(font);
_labelOne.setTargetWidth(100);
_labelTwo = new Label(text);
_labelTwo.setFont(font);
_labelTwo.setTargetHeight(30);
}
public void layout ()
{
super.layout();
// layout our labels
Graphics2D g = (Graphics2D)getGraphics();
_labelZero.layout(g);
System.out.println("l0: " + _labelZero.getSize());
_labelOne.layout(g);
System.out.println("l1: " + _labelOne.getSize());
_labelTwo.layout(g);
System.out.println("l2: " + _labelTwo.getSize());
}
public void paintComponent (Graphics g)
{
super.paintComponent(g);
// render our labels
Graphics2D g2 = (Graphics2D)g;
Dimension size;
int x = 10, y = 10;
g2.setColor(Color.black);
_labelZero.render(g2, x, y);
g2.setColor(Color.red);
size = _labelZero.getSize();
g2.drawRect(x, y, size.width, size.height);
y += 20;
g2.setColor(Color.black);
_labelTwo.render(g2, x, y);
g2.setColor(Color.red);
size = _labelTwo.getSize();
g2.drawRect(x, y, size.width, size.height);
g2.setColor(Color.blue);
g2.drawRect(x, y, size.width, 30);
y += 40;
g2.setColor(Color.black);
_labelOne.render(g2, x, y);
g2.setColor(Color.red);
size = _labelOne.getSize();
g2.drawRect(x, y, size.width, size.height);
g2.setColor(Color.blue);
g2.drawRect(x, y, 100, size.height);
}
public Dimension getPreferredSize ()
{
// lay out label zero if necessary
int width = _labelZero.getSize().width;
if (width == 0) {
Graphics2D g = (Graphics2D)getGraphics();
_labelZero.layout(g);
width = _labelZero.getSize().width;
}
return new Dimension(width + 20, 300);
}
public static void main (String[] args)
{
JFrame frame = new JFrame("Label Demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
LabelDemo demo = new LabelDemo();
frame.getContentPane().add(demo);
frame.pack();
frame.show();
}
protected Label _labelZero;
protected Label _labelOne;
protected Label _labelTwo;
}