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(); } }