From d6dc2e182248cfd054c406ad639cb2f9842f7581 Mon Sep 17 00:00:00 2001 From: mdb Date: Wed, 19 Dec 2001 07:02:50 +0000 Subject: [PATCH] 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 --- .../src/java/com/samskivert/swing/Label.java | 259 ++++++++++++++++++ .../java/com/samskivert/swing/LabelDemo.java | 104 +++++++ 2 files changed, 363 insertions(+) create mode 100644 projects/samskivert/src/java/com/samskivert/swing/Label.java create mode 100644 projects/samskivert/tests/src/java/com/samskivert/swing/LabelDemo.java diff --git a/projects/samskivert/src/java/com/samskivert/swing/Label.java b/projects/samskivert/src/java/com/samskivert/swing/Label.java new file mode 100644 index 00000000..54a3d65f --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/swing/Label.java @@ -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 getBounds() 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; +} diff --git a/projects/samskivert/tests/src/java/com/samskivert/swing/LabelDemo.java b/projects/samskivert/tests/src/java/com/samskivert/swing/LabelDemo.java new file mode 100644 index 00000000..e21c87f3 --- /dev/null +++ b/projects/samskivert/tests/src/java/com/samskivert/swing/LabelDemo.java @@ -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; +}