package net.bioclipse.spectrum.graph2d; import java.awt.*; import java.util.*; import java.lang.*; import java.awt.image.*; /* ************************************************************************** ** ** Class RTextLine ** ************************************************************************** ** 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 an extension of TextLine that allows text to be rotated. ** Currently only multiples of 90 degrees is allowed but the Image filter ** could easily be extended to allow arbitrary rotation. ** *************************************************************************/ /** * * This class is an extension of the TextLine Class * that allows text to be rotated. To rotate the text the Component.createImage * method is used. This means that this class needs to know * the component that will receive the text. If the component is not * known the text cannot be rotated and the class will fall back to the * TextLine.draw method.<P> * * The text is rotated by running the image through an ImageFilter. This filter * can easily be modified to rotate text through an arbitrary angle but * unless the text is large the integer mapping will blur the text. * Also positioning of arbitrarily rotated text becomes problematic. * * @version $Revision: 1.5 $, $Date: 1996/10/23 03:13:45 $ * @author Leigh Brookshaw */ public class RTextLine extends TextLine { /* ************* ** ** Constants ** ************/ /* ********************** ** ** Protected Variables ** *********************/ /** * Rotation Angle of text in degrees */ protected int angle = 0; private double cos = 1.0; private double sin = 0.0; private Component component = null; /* ********************** ** ** Constructors ** *********************/ /** * Instantiate the class */ public RTextLine() { } /** * Instantiate the class. * @param s String to parse. */ public RTextLine(String s) { super(s); } /** * Instantiate the class. * @param c the Component the text will be drawn into. */ public RTextLine(Component c) { setDrawingComponent(c); } /** * Instantiate the class * @param s String to parse. * @param f Font to use. */ public RTextLine(String s, Font f) { super(s,f); } /** * Instantiate the class * @param s String to parse. * @param f Font to use. * @param c Color to use * @param j Justification */ public RTextLine(String s, Font f, Color c, int j) { super(s,f,c,j); } /** * Instantiate the class * @param s String to parse. * @param c Color to use */ public RTextLine(String s, Color c) { super(s,c); } /** * Instantiate the class * @param f Font to use. * @param c Color to use * @param j Justification * @param a Rotation angle in degrees */ public RTextLine(Font f, Color c, int j, int a) { super(f,c,j); setRotation(a); } /** * Instantiate the class * @param f Font to use. * @param c Color to use * @param j Justification */ public RTextLine(Font f, Color c, int j) { super(f,c,j); } /* ***************** ** ** Public Methods ** *****************/ /** * Copy the state of the parsed Textline into the existing * object. * @param t The TextLine to get the state information from. */ public void copyState(RTextLine t) { if(t==null) return; font = t.getFont(); color = t.getColor(); justification = t.getJustification(); setRotation(t.getRotation(),t.getComponent() ); if(font == null) return; fontname = font.getName(); fontstyle = font.getStyle(); fontsize = font.getSize(); parse = true; } /** * Set the text rotation angle. Only multiples of 90 degrees * are accepted * @param angle The angle to rotate the text */ public void setRotation(int angle) { this.angle = ((angle%360)/90)*90; cos = Math.cos(angle*Math.PI/180.0); sin = Math.sin(angle*Math.PI/180.0); } /** * Set the Component the text will be drawn into. This is required to rotate * the text. if it is not set the text will not be rotated. * @param c The drawing component */ public void setDrawingComponent( Component c ) { component = c; } /** * Set the Rotation and the Component that will be drawn into * @param angle The angle to rotate the text * @param c The drawing component */ public void setRotation(int angle, Component c) { setRotation(angle); setDrawingComponent(c); } /** * @return the Rotation angle in degrees. */ public int getRotation() { return angle; } /** * @return the Component that will receive the text. */ public Component getComponent() { return component; } /** * The width of the text after it has been rotated. * @param g Graphics context. * @return the width of the parsed text after it has been rotated. */ public int getRWidth(Graphics g) { parseText(g); return (int)(Math.abs(cos*width+sin*height) + 0.5); } /** * The height of the text after it has been rotated. * @param g Graphics context. * @return the height of the parsed text after it has been rotated. */ public int getRHeight(Graphics g) { parseText(g); return (int)(Math.abs(-sin*width+cos*height) + 0.5); } /** * Return the left edge of the rotated text. * This is dependent on the justification of the text. * @param g Graphics context. * @return the Left edge of the rotated text */ public int getLeftEdge(Graphics g) { return getLeftEdge(g,justification); } /** * Return the right edge of the rotated text. * This is dependent on the justification of the text. * @param g Graphics context. * @return the Right edge of the rotated text */ public int getRightEdge(Graphics g) { return getRightEdge(g,justification); } /** * Return the top edge of the rotated text. * This is dependent on the justification of the text. * @param g Graphics context. * @return the Top edge of the rotated text */ public int getTopEdge(Graphics g) { return getTopEdge(g,justification); } /** * Return the bottom edge of the rotated text. * This is dependent on the justification of the text. * @param g Graphics context. * @return the Bottom edge of the rotated text */ public int getBottomEdge(Graphics g) { return getBottomEdge(g,justification); } /** * Return the left edge of the rotated text. * @param g Graphics context. * @param j Text justification * @return the Left edge of the rotated text */ public int getLeftEdge(Graphics g, int j) { parseText(g); switch (angle) { case 90: case -270: return -ascent; case 180: case -180: if(j == CENTER ) return -width/2; else if(j == RIGHT ) return 0; else return -width; case 270: case -90: return -descent-leading; default: if(j == CENTER ) return -width/2; else if(j == RIGHT ) return -width; else return 0; } } /** * Return the right edge of the rotated text. * @param g Graphics context. * @param j Text justification * @return the Right edge of the rotated text */ public int getRightEdge(Graphics g, int j) { parseText(g); switch (angle) { case 90: case -270: return descent+leading; case 180: case -180: if(j == CENTER ) return width/2; else if(j == RIGHT ) return width; else return 0; case 270: case -90: return ascent; default: if(j == CENTER ) return width/2; else if(j == RIGHT ) return 0; else return width; } } /** * Return the top edge of the rotated text. * @param g Graphics context. * @param j Text justification * @return the Top edge of the rotated text */ public int getTopEdge(Graphics g, int j) { parseText(g); switch (angle) { case 90: case -270: if(j == CENTER ) return width/2; else if(j == RIGHT ) return 0; else return width; case 180: case -180: return descent+leading; case 270: case -90: if(j == CENTER ) return width/2; else if(j == RIGHT ) return width; else return 0; default: return ascent; } } /** * Return the bottom edge of the rotated text. * @param g Graphics context. * @param j Text justification * @return the Bottom edge of the rotated text */ public int getBottomEdge(Graphics g, int j) { parseText(g); switch (angle) { case 90: case -270: if(j == CENTER ) return -width/2; else if(j == RIGHT ) return -width; else return 0; case 180: case -180: return -ascent; case 270: case -90: if(j == CENTER ) return -width/2; else if(j == RIGHT ) return 0; else return -width; default: return -descent-leading; } } /** * Parse the text then draw it. * @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) { if( g == null ) return; if(component == null ) angle = 0; if(angle == 0 ) super.draw(g,x,y); else draw(component,g,x,y); } /** * 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; if(component == null ) angle = 0; if(angle == 0 ) super.draw(g,x,y); else draw(component,g,x,y); } /** * Parse the text, rotate it then draw it to the screen. * @param g Graphics context * @param x pixel position of the text * @param y pixel position of the text */ public synchronized void draw(Component comp, Graphics g, int x, int y) { // TextState ts; // int xoffset = 0; // int yoffset = 0; // Image offsI = null; // Graphics offsG = null; // Image rotatedImage = null; // int maxHeight = 0; // // // if(text == null || comp == null) return; // // // parseText(g); // // maxHeight = maxAscent + maxDescent; // // /* // ** Calculate the offset of the rotated image so that it // ** will be positioned correctly. Remeber the image is calculated // ** on the Maximum Ascent and descent so that no character // ** is truncated // */ // switch (angle) { // // case 90: case -270: // xoffset = -maxAscent; // if(justification == CENTER ) yoffset = - width/2; // else // if(justification == RIGHT ) yoffset = 0; // else yoffset = - width; // break; // case 180: case -180: // yoffset = -maxDescent; // if(justification == CENTER ) xoffset = - width/2; // else // if(justification == RIGHT ) xoffset = 0; // else xoffset = - width; // break; // case 270: case -90: // xoffset = -maxDescent; // if(justification == CENTER ) yoffset = - width/2; // else // if(justification == RIGHT ) yoffset = - width; // else yoffset = 0; // break; // default: // xoffset = 0; // yoffset = 0; // break; // } // /* // ** Create the offscreen image that the text will be written into // */ // offsI = comp.createImage(width,maxHeight); // offsG = offsI.getGraphics(); // /* // ** Color the image with the background color // */ // if(background != null) { // offsG.setColor(background); // } else { // offsG.setColor(comp.getBackground()); // } // // offsG.fillRect(0,0,width,maxHeight); // /* // ** Set the image font and color // */ // offsG.setFont(g.getFont()); // offsG.setColor(g.getColor()); // // if(font != null) offsG.setFont(font); // if(color != null) offsG.setColor(color); // // /* // ** Write to the offscreen image // */ // for(int i=0; i<list.size(); i++) { // ts = ((TextState)(list.elementAt(i))); // if(ts.f != null) offsG.setFont(ts.f); // if(ts.s != null) // offsG.drawString(ts.toString(),ts.x,ts.y+maxAscent ); // } // /* // ** Rotate the en image // */ // // RotateTextFilter f = new RotateTextFilter(angle); // ImageProducer producer = new FilteredImageSource(offsI.getSource(),f); // // rotatedImage = comp.createImage(producer); // /* // ** Draw the rotated image to the Component. Do not notify any // ** image consumer especially the component, otherwise we will get a // ** feedback loop starting up, // ** as this method is normally called from a paint method. // */ // g.drawImage(rotatedImage,x+xoffset,y+yoffset,null); // // } } /** * This is an extension to the ImageFilter class that will rotate an image * a multiple of 90 degrees. This filter is easily extended to allow arbitrary * rotations */ class RotateTextFilter extends ImageFilter { /* ** The angle to rotate the image in degrees */ private int angle = 0; /* ** The minimum and maximum points on the image after rotation. ** More important if arbitrary rotation was allowed */ private int xmin = 0; private int xmax = 0; private int ymin = 0; private int ymax = 0; /* ** The width and height of the New rotated image */ private int width; private int height; /* ** the rotation cosines of the angle */ private double cos = 1.0; private double sin = 0.0; /* ** The arrays that will store the new image. Only one is used depending ** on the data type of the original image. */ private int ipixels[]; private byte bpixels[]; /* ** the color model of the original image */ private ColorModel colorModel; /* ***************** ** Constructors ****************/ /** * Instantiate the class * @param angle the angle to rotate the image. Only multiples of 90 degrees * are allowed */ public RotateTextFilter(int angle) { this.angle = ((angle%360)/90)*90; cos = Math.cos( angle*Math.PI/180.0 ); sin = Math.sin( angle*Math.PI/180.0 ); } /** * Add to the properties table of the image that it has been rotated * @param props The property table of the original image */ public void setProperties(Hashtable props) { props = (Hashtable) props.clone(); props.put("rotAngle", new Integer(angle) ); super.setProperties(props); } /** * Find the dimensions of the original image and pass onto * the image consumer the dimensions of the new roated image * @param w width of the original image * @param h height of the original image */ public void setDimensions(int w, int h) { int x[] = {0,w-1,w-1,0}; int y[] = {0,0,h-1,h-1}; int xx; int yy; for( int i=0; i<4; i++ ) { xx = (int)Math.round( x[i]*cos + y[i]*sin); yy = (int)Math.round(-x[i]*sin + y[i]*cos); xmin = Math.min(xmin,xx); xmax = Math.max(xmax,xx); ymin = Math.min(ymin,yy); ymax = Math.max(ymax,yy); } width = xmax-xmin+1; height = ymax-ymin+1; consumer.setDimensions(width, height); } /** * As the pixels of the original image are sent store them in memory * as the rotated image. * * This method is called with a subset rectangle of the original image. * * @param x position of Left column in original image of this rectangle * @param y poisition of Top row in original image of this rectangle * @param w width of this rectangle * @param h height of this rectangle * @param model Colormodel associated with original image * @param pixels Array containing the image or part of it * @param off The offset into the pixels array where the parsed * rectangle starts * @param scan The actual width of the image. */ public void setPixels(int x, int y, int w, int h, ColorModel model, byte pixels[], int off, int scan) { int i,j,k; int ir,jr; /* ** If the byte array is null create it. Also remember the color ** model so that we can pass it onto the image consumer */ if(bpixels == null) { colorModel = model; bpixels = new byte[width*height]; } /* ** place the rotated image into memory one pixel at a time */ j = y; for(int n=0; n<h; n++, j++) { i = x; for(int m=0; m<w; m++, i++) { ir = (int)Math.round( i*cos + j*sin) - xmin; jr = (int)Math.round(-i*sin + j*cos) - ymin; k = ir+jr*width; bpixels[k] = pixels[ (j-y)*scan+(i-x)+off ]; } } } /** * As the pixels of the original image are sent store them in memory * as the rotated image. * * This method is called with a subset rectangle of the original image. * * @param x position of Left column in original image of this rectangle * @param y poisition of Top row in original image of this rectangle * @param w width of this rectangle * @param h height of this rectangle * @param model Colormodel associated with original image * @param pixels Array containing the image or part of it * @param off The offset into the pixels array where the parsed * rectangle starts * @param scan The actual width of the image. */ public void setPixels(int x, int y, int w, int h, ColorModel model, int pixels[], int off, int scan) { int i,j,k; int ir,jr; /* ** If the integer array is null create it. Also remember the color ** model so that we can pass it onto the image consumer */ if(ipixels == null) { colorModel = model; ipixels = new int[width*height]; } /* ** place the rotated image into memory one pixel at a time */ j = y; for(int n=0; n<h; n++, j++) { i = x; for(int m=0; m<w; m++, i++) { ir = (int)Math.round( i*cos + j*sin) - xmin; jr = (int)Math.round(-i*sin + j*cos) - ymin; k = ir+jr*width; ipixels[k] = pixels[ (j-y)*scan+(i-x)+off ]; } } } /** * Called when the image is complete. * When this is called by the image producer, we can then pass the rotated * image onto the image consumer. * * @param status Status of the original image from the image producer. */ public void imageComplete(int status) { if(status == ImageConsumer.IMAGEABORTED || status == ImageConsumer.IMAGEERROR ) { consumer.imageComplete(status); ipixels = null; bpixels = null; return; } /* ** Send the rotated image to the image consumer. Not forgetting to tell ** it when the image is complete */ if(bpixels != null) { for(int j=0; j<height; j++) consumer.setPixels(0, j, width, 1, colorModel, bpixels, j*width, width); consumer.imageComplete(status); } else if(ipixels != null) { for(int j=0; j<height; j++) consumer.setPixels(0, j, width, 1, colorModel, ipixels, j*width, width); consumer.imageComplete(status); } else consumer.imageComplete(ImageConsumer.IMAGEABORTED); ipixels = null; bpixels = null; } }