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:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user