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