package net.bioclipse.spectrum.graph2d;
import java.awt.*;
import java.util.*;
import java.lang.*;
import java.awt.image.*;
/*
**************************************************************************
**
** Class TextLine
**
**************************************************************************
** Copyright (C) 1996 Leigh Brookshaw
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**************************************************************************
**
** This class is designed to bundle together all the information required
** to draw short Strings
**
*************************************************************************/
/**
* This class is designed to bundle together all the information required
* to draw short Strings with subscripts and superscripts
*
* @version $Revision: 1.10 $, $Date: 1996/09/05 04:53:27 $
* @author Leigh Brookshaw
*/
public class TextLine extends Object {
/*
*************
**
** Constants
**
************/
/**
* Center the Text over the point
*/
public final static int CENTER = 0;
/**
* Position the Text to the Left of the point
*/
public final static int LEFT = 1;
/**
* Position the Text to the Right of the point
*/
public final static int RIGHT = 2;
/**
* Format to use when parsing a double
*/
public final static int SCIENTIFIC = 1;
/**
* Format to use when parsing a double
*/
public final static int ALGEBRAIC = 2;
/*
** Minimum Point size allowed for script characters
*/
final static int MINIMUM_SIZE = 6;
/*
**********************
**
** Protected Variables
**
*********************/
/**
* Decrease in size of successive script levels
*/
protected double script_fraction = 0.8;
/**
* Superscript offset
*/
protected double sup_offset = 0.6;
/**
* Subscript offset
*/
protected double sub_offset = 0.7;
/**
* Font to use for text
*/
protected Font font = null;
/**
* Text color
*/
protected Color color = null;
/**
* Background Color
*/
protected Color background = null;
/**
* The text to display
*/
protected String text = null;
/**
* The logical name of the font to use
*/
protected String fontname = "TimesRoman";
/**
* The font size
*/
protected int fontsize = 0;
/**
* The font style
*/
protected int fontstyle = Font.PLAIN;
/**
* Text justification. Either CENTER, LEFT or RIGHT
*/
protected int justification = LEFT;
/**
* The width of the text using the current Font
*/
protected int width = 0;
/**
* The ascent using the current font
*/
protected int ascent = 0;
/**
* The maximum ascent using the current font
*/
protected int maxAscent = 0;
/**
* The descent using the current font
*/
protected int descent = 0;
/**
* The maximum descent using the current font
*/
protected int maxDescent = 0;
/**
* The height using the current font ie ascent+descent+leading
*/
protected int height = 0;
/**
* The leading using the current font
*/
protected int leading = 0;
/**
* Has the string been parsed! This only needs to be done once
* unless the font is altered.
*/
protected boolean parse = true;
/**
* Local graphics context.
*/
protected Graphics lg = null;
/**
* The parsed string. Each element in the vector represents
* a change of context in the string ie font change and offset.
*/
protected Vector list = new Vector(8,4);
/*
**********************
**
** Constructors
**
*********************/
/**
* Instantiate the class
*/
public TextLine() { }
/**
* Instantiate the class.
* @param s String to parse.
*/
public TextLine(String s) {
this.text = s;
}
/**
* Instantiate the class
* @param s String to parse.
* @param f Font to use.
*/
public TextLine(String s, Font f) {
this(s);
font = f;
if(font == null) return;
fontname = f.getName();
fontstyle = f.getStyle();
fontsize = f.getSize();
}
/**
* Instantiate the class
* @param s String to parse.
* @param f Font to use.
* @param c Color to use
* @param j Justification
*/
public TextLine(String s, Font f, Color c, int j) {
this(s,f);
color = c;
justification = j;
}
/**
* Instantiate the class
* @param s String to parse.
* @param c Color to use
*/
public TextLine(String s, Color c) {
this(s);
color = c;
}
/**
* Instantiate the class
* @param f Font to use.
* @param c Color to use
* @param j Justification
*/
public TextLine(Font f, Color c, int j) {
font = f;
color = c;
justification = j;
if(font == null) return;
fontname = f.getName();
fontstyle = f.getStyle();
fontsize = f.getSize();
}
/*
*****************
**
** Public Methods
**
*****************/
/**
* Create a New Textline object copying the state of the existing
* object into the new one. The state of the class is the color,
* font, and justification ie everything but the string.
*/
public TextLine copyState() {
return new TextLine(font,color,justification);
}
/**
* Copy the state of the parsed Textline into the existing
* object.
* @param t The TextLine to get the state information from.
*/
public void copyState(TextLine t) {
if(t==null) return;
font = t.getFont();
color = t.getColor();
justification = t.getJustification();
if(font == null) return;
fontname = font.getName();
fontstyle = font.getStyle();
fontsize = font.getSize();
parse = true;
}
/**
* Set the Font to use with the class
* @param f Font
*/
public void setFont( Font f ) {
font = f;
fontname = f.getName();
fontstyle = f.getStyle();
fontsize = f.getSize();
parse = true;
}
/**
* Set the String to use with the class
* @param s String
*/
public void setText( String s ) {
text = s;
parse = true;
}
/**
* Set the Color to use with the class
* @param c Color
*/
public void setColor( Color c ) {
color = c;
}
/**
* Set the Background Color to use with the class
* @param c Color
*/
public void setBackground( Color c ) {
background = c;
}
/**
* Set the Justification to use with the class
* @param t Justification
*/
public void setJustification( int i ) {
switch (i) {
case CENTER:
justification = CENTER;
break;
case LEFT: default:
justification = LEFT;
break;
case RIGHT:
justification = RIGHT;
break;
}
}
/**
* @return the font the class is using
*/
public Font getFont() {
return font;
}
/**
* @return the String the class is using.
*/
public String getText() {
return text;
}
/**
* @return the Color the class is using.
*/
public Color getColor() {
return color;
}
/**
* @return the Background Color the class is using.
*/
public Color getBackground() {
return background;
}
/**
* @return the Justification the class is using.
*/
public int getJustification() {
return justification;
}
/**
* @param g Graphics context.
* @return the Fontmetrics the class is using.
*/
public FontMetrics getFM(Graphics g) {
if(g==null) return null;
if(font==null) return g.getFontMetrics();
else return g.getFontMetrics(font);
}
/**
* @param g Graphics context.
* @param ch The character.
* @return the width of the character.
*/
public int charWidth(Graphics g, char ch) {
FontMetrics fm;
if(g==null) return 0;
if(font==null) fm = g.getFontMetrics();
else fm = g.getFontMetrics(font);
return fm.charWidth(ch);
}
/**
* @param g Graphics context.
* @return the width of the parsed text.
*/
public int getWidth(Graphics g) {
parseText(g);
return width;
}
/**
* @param g Graphics context.
* @return the height of the parsed text.
*/
public int getHeight(Graphics g) {
parseText(g);
return height;
}
/**
* @param g Graphics context.
* @return the ascent of the parsed text.
*/
public int getAscent(Graphics g) {
if(g == null) return 0;
parseText(g);
return ascent;
}
/**
* @param g Graphics context.
* @return the maximum ascent of the parsed text.
*/
public int getMaxAscent(Graphics g) {
if(g == null) return 0;
parseText(g);
return maxAscent;
}
/**
* @param g Graphics context.
* @return the descent of the parsed text.
*/
public int getDescent(Graphics g) {
if(g == null) return 0;
parseText(g);
return descent;
}
/**
* @param g Graphics context.
* @return the maximum descent of the parsed text.
*/
public int getMaxDescent(Graphics g) {
if(g == null) return 0;
parseText(g);
return maxDescent;
}
/**
* @param g Graphics context.
* @return the leading of the parsed text.
*/
public int getLeading(Graphics g) {
if(g == null) return 0;
parseText(g);
return leading;
}
/**
* parse the text. When the text is parsed the width, height, leading
* are all calculated. The text will only be truly parsed if
* the graphics context has changed or the text has changed or
* the font has changed. Otherwise nothing is done when this
* method is called.
* @param g Graphics context.
*/
public void parseText(Graphics g) {
FontMetrics fm;
TextState current = new TextState();
char ch;
Stack state = new Stack();
int w = 0;
if(lg != g) parse = true;
lg = g;
if(!parse) return;
parse = false;
width = 0;
leading = 0;
ascent = 0;
descent = 0;
height = 0;
maxAscent = 0;
maxDescent = 0;
if( text == null || g == null) return;
list.removeAllElements();
if(font == null) current.f = g.getFont();
else current.f = font;
state.push(current);
list.addElement(current);
fm = g.getFontMetrics(current.f);
for(int i=0; i<text.length(); i++) {
ch = text.charAt(i);
switch (ch) {
case '$':
i++;
if(i<text.length()) current.s.append(text.charAt(i));
break;
/*
** Push the current state onto the state stack
** and start a new storage string
*/
case '{':
w = current.getWidth(g);
if(!current.isEmpty()) {
current = current.copyState();
list.addElement(current);
}
state.push(current);
current.x += w;
break;
/*
** Pop the state off the state stack and set the current
** state to the top of the state stack
*/
case '}':
w = current.x + current.getWidth(g);
state.pop();
current = ((TextState)state.peek()).copyState();
list.addElement(current);
current.x = w;
break;
case '^':
w = current.getWidth(g);
if(!current.isEmpty()) {
current = current.copyState();
list.addElement(current);
}
current.f = getScriptFont(current.f);
current.x += w;
current.y -= (int)((double)(current.getAscent(g))*sup_offset+0.5);
break;
case '_':
w = current.getWidth(g);
if(!current.isEmpty()) {
current = current.copyState();
list.addElement(current);
}
current.f = getScriptFont(current.f);
current.x += w;
current.y += (int)((double)(current.getDescent(g))*sub_offset+0.5);
break;
default:
current.s.append(ch);
break;
}
}
for(int i=0; i<list.size(); i++) {
current = ((TextState)(list.elementAt(i)));
if( !current.isEmpty() ) {
width += current.getWidth(g);
ascent = Math.max(ascent, Math.abs(current.y) +
current.getAscent(g));
descent = Math.max(descent, Math.abs(current.y) +
current.getDescent(g));
leading = Math.max(leading, current.getLeading(g));
maxDescent = Math.max(maxDescent, Math.abs(current.y) +
current.getMaxDescent(g));
maxAscent = Math.max(maxAscent, Math.abs(current.y) +
current.getMaxAscent(g));
}
}
height = ascent+descent+leading;
return;
}
/**
* @return true if the text has never been set or is null
*/
public boolean isNull() {
return (text==null);
}
/**
* Parse the text then draw it.
* @param g Graphics context
* @param x pixel position of the text
* @param y pixel position of the text
* @param j justification of the text
*/
public void draw(Graphics g, int x, int y, int j) {
justification = j;
if( g == null ) return;
draw(g,x,y);
}
/**
* Parse the text then draw it without any rotation.
* @param g Graphics context
* @param x pixel position of the text
* @param y pixel position of the text
*/
public void draw(Graphics g, int x, int y) {
TextState ts;
int xoffset = x;
int yoffset = y;
if(g == null || text == null) return;
Graphics lg = g.create();
parseText(g);
if(justification == CENTER ) {
xoffset = x-width/2;
} else
if(justification == RIGHT ) {
xoffset = x-width;
}
if(background != null) {
lg.setColor(background);
lg.fillRect(xoffset,yoffset-ascent,width,height);
lg.setColor(g.getColor());
}
if(font != null) lg.setFont(font);
if(color != null) lg.setColor(color);
for(int i=0; i<list.size(); i++) {
ts = ((TextState)(list.elementAt(i)));
if(ts.f != null) lg.setFont(ts.f);
if(ts.s != null)
lg.drawString(ts.toString(),ts.x+xoffset,ts.y+yoffset);
}
lg.dispose();
lg = null;
}
/**
* @return Logical font name of the set font
*/
public String getFontName() { return fontname; }
/**
* @return Style of the set font
*/
public int getFontStyle() { return fontstyle; }
/**
* @return Size of the set font
*/
public int getFontSize() { return fontsize; }
/**
* Set the Logical font name of the current font
* @param s Logical font name.
*/
public void setFontName(String s) { fontname = s; rebuildFont(); }
/**
* Set the Font style of the current font
* @param i Font style.
*/
public void setFontStyle(int i) { fontstyle = i; rebuildFont(); }
/**
* Set the Font size of the current font
* @param i Font size.
*/
public void setFontSize(int i) { fontsize = i; rebuildFont(); }
/*
** Rebuild the font using the current fontname, fontstyle, and fontsize.
*/
private void rebuildFont() {
parse = true;
if( fontsize <= 0 || fontname == null ) {
font = null;
} else {
font = new Font(fontname, fontstyle, fontsize);
}
}
/**
* @param f Font
* @return The script font version of the parsed font using the
* script_fraction variable.
*/
public Font getScriptFont(Font f) {
int size;
if(f == null) return f;
size = f.getSize();
if(size <= MINIMUM_SIZE) return f;
size = (int)((double)(f.getSize())*script_fraction + 0.5);
if(size <= MINIMUM_SIZE) return f;
return new Font(f.getName(), f.getStyle(), size);
}
/**
* Parse a double value. Precision is 6 figures, with 7 significant
* figures.
* @param d double to parse
* return <i>true</i> if the parse was successful
*/
public boolean parseDouble(double d) {
return parseDouble(d, 7, 6, ALGEBRAIC);
}
/**
* Parse a double value. Number of significant figures is 1 greater than
* the precision.
* @param d double to parse
* @param p precision of the number
* return <i>true</i> if the parse was successful
*/
public boolean parseDouble(double d, int p) {
return parseDouble(d, p+1, p, ALGEBRAIC);
}
/**
* Parse a double value
* @param d double to parse
* @param n number of significant figures
* @param p precision of the number
* @param f format of the number scientific, algebraic etc.
* return <i>true</i> if the parse was successful
*/
public boolean parseDouble(double d, int n, int p, int f) {
double x = d;
int left = n - p;
double right = 0;
int power;
int exponent;
int i;
StringBuffer s = new StringBuffer(n+4);
if(left < 0 ) {
System.out.println(
"TextLine.parseDouble: Precision > significant figures!");
return false;
}
if(d < 0.0) {
x = -d;
s.append("-");
}
//System.out.println("parseDouble: value = "+x);
if( d == 0.0 ) exponent = 0;
else exponent = (int)(Math.floor(SpecialFunction.log10(x)));
//System.out.println("parseDouble: exponent = "+exponent);
power = exponent - (left - 1);
//System.out.println("parseDouble: power = "+power);
if( power < 0 ) {
for(i=power; i<0; i++) { x *= 10.0; }
} else {
for(i=0; i<power; i++) { x /= 10.0; }
}
//System.out.println("parseDouble: adjusted value = "+x);
left = (int)x;
s.append(left);
//System.out.println("parseDouble: left = "+left);
if( p > 0 ) {
s.append('.');
right = x-left;
for(i=0; i<p; i++) {
right *= 10;
if(i==p-1) right += 0.5;
s.append((int)(right));
right -= (int)right;
}
}
//System.out.println("parseDouble: right = "+right);
if(power != 0) {
if(f == SCIENTIFIC) {
s.append('E');
if(power < 0) s.append('-');
else s.append('+');
power = Math.abs(power);
if(power > 9) {
s.append(power);
} else {
s.append('0');
s.append(power);
}
} else {
s.append("x10{^");
s.append(power);
s.append("}");
}
}
setText( s.toString() );
return true;
}
}
/**
* A structure class used exclusively with the TextLine class.
* When the Text changes state (new font, new color, new offset)
* then this class holds the information plus the substring
* that the state pertains to.
*/
class TextState extends Object {
Font f = null;
StringBuffer s = null;
int x = 0;
int y = 0;
public TextState() {
s = new StringBuffer();
}
public TextState copyAll() {
TextState tmp = copyState();
if(s.length()==0) return tmp;
for(int i=0; i<s.length(); i++) { tmp.s.append(s.charAt(i)); }
return tmp;
}
public TextState copyState() {
TextState tmp = new TextState();
tmp.f = f;
tmp.x = x;
tmp.y = y;
return tmp;
}
public String toString() {
return s.toString();
}
public boolean isEmpty() {
return (s.length() == 0);
}
public int getWidth(Graphics g) {
if(g == null || f == null || s.length()==0 ) return 0;
return g.getFontMetrics(f).stringWidth(s.toString());
}
public int getHeight(Graphics g) {
if(g == null || f == null ) return 0;
return g.getFontMetrics(f).getHeight();
}
public int getAscent(Graphics g) {
if(g == null || f == null ) return 0;
return g.getFontMetrics(f).getAscent();
}
public int getDescent(Graphics g) {
if(g == null || f == null ) return 0;
return g.getFontMetrics(f).getDescent();
}
public int getMaxAscent(Graphics g) {
if(g == null || f == null ) return 0;
return g.getFontMetrics(f).getMaxAscent();
}
public int getMaxDescent(Graphics g) {
if(g == null || f == null ) return 0;
return g.getFontMetrics(f).getMaxDescent();
}
public int getLeading(Graphics g) {
if(g == null || f == null ) return 0;
return g.getFontMetrics(f).getLeading();
}
}