home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Personal Computer World 2001 July
/
PCWJUL01.iso
/
system
/
sys_home
/
PhantomScroll.java
< prev
next >
Wrap
Text File
|
2000-05-23
|
54KB
|
1,479 lines
/*
* @(#)PhantomScroll.java 1.0 25 Jan 2000
* Copyright (c) 2000 Uldarico Muico Jr.
*
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
import java.applet.*;
import java.awt.*;
import java.awt.image.*;
import java.net.*;
import java.io.*;
import java.util.*;
/**
* The PhantomScroll applet is a horizontal text scroller with fade effects.
* @version 1.0 25 Jan 2000
* @author Uldarico Muico Jr.
*/
public class PhantomScroll extends Applet implements Runnable
{
/**
* Ease-of-use constant for bgDisplay parameter of setBackground method. Indicates that the
* background image should be tiled.
* @see #setBackground(Image, Color, int)
*/
public static final int TILE = 0;
/**
* Ease-of-use constant for bgDisplay parameter of setBackground method. Indicates that the
* background image should be centered.
* @see #setBackground(Image, Color, int)
*/
public static final int CENTER = 1;
private Thread scrollThread = null;
private int[] bgPixels;
private int[] fgPixels;
private int[] backPixels;
private int[] canvasPixels;
private int[] textWidths;
private int[][] fadePixels;
private int[][] textPixels;
private int[][][] linkRanges;
private Hashtable[][] links;
private Dimension bgSize = new Dimension();
private Dimension fgSize = new Dimension();
private Image bgImage;
private Image fgImage;
private int textHeight = 0;
private int currentText = 0;
private int width;
private int height;
private int offset;
private int bgDisplay;
private int textDisp;
private int speed;
private int delay;
private int fadeIndex = 0;
private int clearAmount;
private int count;
private int textCount = 0;
private int currentLink = -1;
private int pressedLink = -1;
private int mouseX;
private int mouseY;
private int amplitude;
private int period;
private Color bgColor;
private Color fgColor;
private Color linkColor;
private Color activeLinkColor;
private boolean fadeIncremented = true;
private boolean pressed;
private boolean entered = false;
private String defaultTarget;
/**
* Returns information about this applet.
* @return the information
*/
public String getAppletInfo()
{
return "Uldarico Muico Jr., um@mail.com\n\r"
+ "PhantomScroll 1.0 January 25, 2000\n\r"
+ "Copyright (c) 2000 Uldarico Muico, Jr.\n\r";
}
/**
* Returns information about the parameters that are understood by this applet.
* @return the information
*/
public String[][] getParameterInfo()
{
String[][] info = { { "text", "HTML", "message to be displayed" },
{ "textFile", "URL", "text file that contains message" },
{ "font", "String", "name of font" },
{ "fontStyle", "0-4", "font style" },
{ "fontSize", "int", "font size" },
{ "bgImage", "URL", "background image" },
{ "bgDisplay", "0-1", "method of displaying background image" },
{ "bgColor", "RGB", "background color" },
{ "fgImage", "URL", "foreground/text image pattern" },
{ "fgColor", "RGB", "foreground/text color" },
{ "linkColor", "RGB", "color of hyperlinks" },
{ "activeLinkColor", "RGB", "color of active hyperlinks" },
{ "speed", "int", "average pixels per iteration" },
{ "delay", "int", "milliseconds per iteration" },
{ "amplitude", "int", "amplitude (pixels) of oscillation" },
{ "period", "int", "period (iterations) per oscillation" },
{ "trail", "0.0-1.0", "intensity of text trail" },
{ "target", "String", "name of the default target frame" } };
return info;
}
/**
* Initializes the applet. This method loads and processes the parameters.
*/
public void init()
{
String value;
width = size().width;
height = size().height;
linkColor = parseColor(getParameter("linkColor"), new Color(0x0000FF));
activeLinkColor = parseColor(getParameter("activeLinkColor"), new Color(0xFF0000));
setForeground(parseColor(getParameter("fgColor"), new Color(0xFFFFFF)));
setBackground(parseColor(getParameter("bgColor"), new Color(0x000000)));
setTarget(getParameter("target"));
value = getParameter("font");
Font font = new Font((value == null) ? "Helvetica" : value,
parseInt(getParameter("fontStyle"), Font.PLAIN),
parseInt(getParameter("fontSize"), 12));
value = getParameter("text");
if (value != null)
setText(value, font);
else
setText(parseURL(getParameter("textFile"), null, true), font);
value = getParameter("bgImage");
if (value != null)
setBackground(parseImage(value, null), null, parseInt(getParameter("bgDisplay"), TILE) % 2);
value = getParameter("fgImage");
if (value != null)
setForeground(parseImage(value, null));
setSpeed(parseInt(getParameter("speed"), 5));
setDelay(parseInt(getParameter("delay"), 80));
setAmplitude(parseInt(getParameter("amplitude"), 6));
setPeriod(parseInt(getParameter("period"), 30));
setTrail(parseUnity(getParameter("trail"), 0.8));
}
/**
* Starts the scrolling animation.
* @see #stop()
*/
public void start()
{
if (scrollThread == null)
{
scrollThread = new Thread(this);
scrollThread.start();
}
}
/**
* Stops the scrolling animation.
* @see #start()
*/
public void stop()
{
scrollThread = null;
}
/**
* Invoked when the thread is started through a call to the <i>start</i> method.
* This method animates the scroller.
* @see #start()
*/
public void run()
{
while (scrollThread != null)
{
repaint();
count++;
if (!pressed)
{
int disp = -speed;
if (period != 0)
disp -= (int) (amplitude * Math.cos(2.0 * Math.PI * count / period));
handleDisplacement(disp);
}
if (fadeIncremented)
fadeIndex++;
else
fadeIndex--;
if (fadeIndex >= fadePixels.length)
{
fadeIndex--;
fadeIncremented = false;
}
else if (fadeIndex < 0)
{
fadeIndex++;
fadeIncremented = true;
}
try
{
scrollThread.sleep(delay);
}
catch (InterruptedException e)
{
scrollThread = null;
}
}
}
/**
* Performs a binary search of the hyperlink to which the cursor is pointing.
* @return the index of the link
*/
private int searchActiveLink()
{
int top = linkRanges[currentText].length - 1;
int bottom = 0;
while (bottom <= top)
{
int middle = (top + bottom) / 2;
int position = mouseX - offset;
if (position < linkRanges[currentText][middle][0])
top = middle - 1;
else if (position > linkRanges[currentText][middle][1])
bottom = middle + 1;
else
return middle;
}
return -1;
}
/**
* Redraws the portion of the canvas that has been affected since the last animation frame.
* @param g the graphics context
* @see #paint(Graphics)
*/
public void update(Graphics g)
{
if (textPixels != null)
{
int prevLink = currentLink;
if (entered && textDisp <= mouseY && mouseY <= textDisp + textHeight && linkRanges[currentText].length > 0)
{
currentLink = searchActiveLink();
if (currentLink != prevLink)
showStatus((currentLink == -1) ? "" : ((String) links[currentText][currentLink].get("alt")));
}
else if (prevLink >= 0)
{
showStatus("");
currentLink = -1;
}
overlayPixels(backPixels, width, textHeight, 0, 0, width, textHeight,
canvasPixels, width, textHeight, 0, 0, true, false);
overlayPixels(textPixels[currentText], textWidths[currentText], textHeight,
0, 0, textWidths[currentText], textHeight,
canvasPixels, width, textHeight, offset, 0, false, true);
Image textImage = createImage(new MemoryImageSource(width, textHeight, canvasPixels, 0, width));
g.drawImage(textImage, 0, textDisp, null);
textImage.flush();
}
}
/**
* Paints the component. This method is called when the contents of the component should be
* painted in response to the component first being shown or damage needing repair.
* @param g the graphics context
* @see #update(Graphics)
*/
public void paint(Graphics g)
{
if (bgPixels != null)
{
if (bgDisplay == TILE)
for (int j = 0; j < height; j += bgSize.height)
for (int i = 0; i < width; i += bgSize.width)
g.drawImage(bgImage, i, j, bgColor, null);
else if (bgDisplay == CENTER)
{
g.setColor(bgColor);
g.fillRect(0, 0, width, height);
g.drawImage(bgImage, (width - bgSize.width) / 2, (height - bgSize.height) / 2, null);
}
}
else
{
g.setColor(bgColor);
g.fillRect(0, 0, width, height);
}
update(g);
}
/**
* Paints the component and returns as soon as the component has completed painting itself.
*/
private void paint()
{
Graphics graphics = getGraphics();
if (graphics != null)
{
paint(graphics);
graphics.dispose();
}
}
/**
* Repaints the component when the image has changed. This method begins processing the
* background and foreground images after it has been notified that they have been completely
* loaded.
* @param img the image being observed
* @param flags see imageUpdate for more information
* @param x the x coordinate
* @param y the y coordinate
* @param width the width
* @param height the height
* @return true if the flags indicate that the image is completely loaded; false otherwise
*/
public boolean imageUpdate(Image img, int flags, int x, int y, int w, int h)
{
if ((flags & ALLBITS) != 0)
{
if (img == bgImage)
setBackground(bgImage, bgColor, bgDisplay);
else if (img == fgImage)
setForeground(fgImage);
}
return super.imageUpdate(img, flags, x, y, w, h);
}
/**
* Determines the current text and offset
* @param disp the displacement on the current offset
*/
private void handleDisplacement(int disp)
{
offset += disp;
if (offset + textWidths[currentText] < 0)
{
offset = width + offset + textWidths[currentText];
currentText++;
if (currentText == textCount)
currentText = 0;
}
else if (offset > width)
{
currentText--;
if (currentText < 0)
currentText = textCount - 1;
offset = offset - width - textWidths[currentText];
}
}
/**
* This method is called when the mouse first enters this component.
* @param event the event that caused the action
* @param x the x coordinate
* @param y the y coordiante
* @return true
*/
public boolean mouseEnter(Event event, int x, int y)
{
mouseX = x;
mouseY = y;
entered = true;
return true;
}
/**
* This method is called when the mouse exits this component.
* @param event the event that caused the action
* @param x the x coordinate
* @param y the y coordiante
* @return true
*/
public boolean mouseExit(Event event, int x, int y)
{
entered = false;
return true;
}
/**
* This method is called when the mouse button is released inside this component.
* @param event the event that caused the action
* @param x the x coordinate
* @param y the y coordiante
* @return true
*/
public boolean mouseUp(Event event, int x, int y)
{
pressed = false;
if (currentLink >= 0 && currentLink == pressedLink)
{
String target = (String) links[currentText][currentLink].get("target");
getAppletContext().showDocument(
(URL) links[currentText][currentLink].get("href"),
(target == null) ? defaultTarget : target);
}
return true;
}
/**
* This method is called when the mouse button is pushed inside this component.
* @param event the event that caused the action
* @param x the x coordinate
* @param y the y coordiante
* @return true
*/
public boolean mouseDown(Event event, int x, int y)
{
pressed = true;
pressedLink = (currentLink >= 0) ? currentLink : -1;
return true;
}
/**
* This method is called when the mouse is moved inside this component with the mouse button
* not pushed.
* @param event the event that caused the action
* @param x the x coordinate
* @param y the y coordiante
* @return true
*/
public boolean mouseMove(Event event, int x, int y)
{
mouseX = x;
mouseY = y;
if (scrollThread == null)
repaint();
return true;
}
/**
* This method is called when the mouse button is moved inside this component with the button
* pushed.
* @param event the event that caused the action
* @param x the x coordinate
* @param y the y coordiante
* @return true
*/
public boolean mouseDrag(Event event, int x, int y)
{
handleDisplacement(x - mouseX);
mouseX = x;
mouseY = y;
repaint();
return true;
}
/**
* Resizes this component to the specified width and height.
* @param w the width
* @param h the height
*/
public void resize(int w, int h)
{
width = w;
height = h;
super.resize(w, h);
width = size().width;
height = size().height;
updateBackground();
}
/**
* Determines the preferred size of the component.
* @return the preferred size of this component
*/
public Dimension preferredSize()
{
return new Dimension(width, height);
}
/**
* Returns the background color.
* @return the background color
*/
public Color getBackgroundColor()
{
return new Color(bgColor.getRGB());
}
/**
* Returns the background image.
* @return the background image
*/
public Image getBackgroundImage()
{
return bgImage;
}
/**
* Returns the foreground color.
* @return the foreground color
*/
public Color getForegroundColor()
{
return new Color(fgColor.getRGB());
}
/**
* Returns the foreground image.
* @return the foreground image
*/
public Image getForegroundImage()
{
return fgImage;
}
/**
* Returns the hyperlink color.
* @return the hyperlink color
*/
public Color getLinkColor()
{
return new Color(linkColor.getRGB());
}
/**
* Returns the active hyperlink color.
* @return the active hyperlink color
*/
public Color getActiveLinkColor()
{
return new Color(activeLinkColor.getRGB());
}
/**
* Returns the speed
* @return the number of pixels to shift between frames/iterations
*/
public int getSpeed()
{
return speed;
}
/**
* Returns the delay between frames/iterations.
* @return the delay in milliseconds
*/
public int getDelay()
{
return delay;
}
/**
* Returns the amplitude of the oscillation.
* @return the number of pixels by which to oscillate in either direction
*/
public int getAmplitude()
{
return amplitude;
}
/**
* Returns the period of the oscillation.
* @return the number of frames/iteration to complet an oscillation
*/
public int getPeriod()
{
return period;
}
/**
* Returns the default target frame.
* @return the name of default target frame
*/
public String getTarget()
{
return new String(defaultTarget);
}
/**
* Updates the background pixels. This method should be called when any aspect of the
* background has changed.
*/
private void updateBackground()
{
if (textPixels != null)
{
textDisp = (height - textHeight) / 2;
backPixels = new int[width * textHeight];
canvasPixels = new int[width * textHeight];
int bgPixel = bgColor.getRGB();
int i, j;
for (i = 0; i < width * textHeight; i++)
canvasPixels[i] = 0xFF000000 | bgPixel;
if (bgPixels != null)
{
if (bgDisplay == TILE)
for (j = -(textDisp % bgSize.height); j < textHeight; j += bgSize.height)
for (i = 0; i < width; i += bgSize.width)
overlayPixels(bgPixels, bgSize.width, bgSize.height, 0, 0, bgSize.width, bgSize.height,
canvasPixels, width, textHeight, i, j, false, false);
else if (bgDisplay == CENTER)
overlayPixels(bgPixels, bgSize.width, bgSize.height, 0, 0, bgSize.width, bgSize.height,
canvasPixels, width, textHeight,
(width - bgSize.width) / 2, (textHeight - bgSize.height) / 2, false, false);
}
for (i = 0; i < width * textHeight; i++)
backPixels[i] = 0xFF000000 | (0xFFFFFF & canvasPixels[i]);
}
}
/**
* Sets the background image.
* @param image the image to be displayed in the background
* @param color the background color to use for any transparent pixels
* @param display the method by which to display the image
* @see #TILE
* @see #CENTER
*/
public void setBackground(Image image, Color color, int display)
{
bgImage = image;
bgDisplay = display % 2;
if (color != null)
bgColor = new Color(color.getRGB());
if (prepareImage(image, this))
{
bgPixels = getPixels(bgImage);
bgSize.width = bgImage.getWidth(null);
bgSize.height = bgImage.getHeight(null);
updateBackground();
paint();
}
}
/**
* Sets the background to a solid color.
* @param color the background color
*/
public void setBackground(Color color)
{
bgColor = new Color(color.getRGB());
bgPixels = null;
bgImage = null;
updateBackground();
paint();
}
/**
* Sets the foreground image pattern.
* @param image the foreground image
*/
public void setForeground(Image image)
{
fgImage = image;
if (prepareImage(image, this))
{
fgPixels = getPixels(fgImage);
fgSize.width = fgImage.getWidth(null);
fgSize.height = fgImage.getHeight(null);
updateForeground();
repaint();
}
}
/**
* Sets the foreground to a solid color.
* @param color the foreground color
*/
public void setForeground(Color color)
{
fgColor = new Color(color.getRGB());
fgPixels = null;
updateForeground();
repaint();
}
/**
* Updates the foreground pixels. This method should be called when any aspect of the
* foreground has changed.
*/
private void updateForeground()
{
if (textPixels != null)
{
if (fgPixels != null)
{
for (int k = 0; k < textCount; k++)
for (int j = 0; j < textHeight; j++)
for (int i = 0; i < textWidths[k]; i++)
{
int index = j * textWidths[k] + i;
textPixels[k][index] = (textPixels[k][index] & 0xFF000000)
| (0xFFFFFF & fgPixels[(j % fgSize.height) * fgSize.width + (i % fgSize.width)]);
}
}
else
{
int pixel = 0xFFFFFF & fgColor.getRGB();
for (int k = 0; k < textCount; k++)
for (int i = 0; i < textWidths[k] * textHeight; i++)
textPixels[k][i] = (textPixels[k][i] & 0xFF000000) | pixel;
}
for (int i = 0; i < textCount; i++)
for (int j = 0; j < linkRanges[i].length; j++)
{
int w = linkRanges[i][j][1] - linkRanges[i][j][0] + 1;
colorPixels(textPixels[i], textWidths[i], textHeight, linkRanges[i][j][0] - w / 8, 0,
5 * w / 4, textHeight, linkColor.getRGB());
}
}
}
/**
* Sets the color of the hyperlinks.
* @param color the hyperlink color
*/
public void setLinkColor(Color color)
{
linkColor = new Color(color.getRGB());
updateForeground();
}
/**
* Sets the color of the hyperlink to which the mouse cursor is pointing
* @param color the hyperlink color
*/
public void setActiveLinkColor(Color color)
{
activeLinkColor = new Color(color.getRGB());
}
/**
* Sets the speed of the scrolling motion
* @param speed the number of pixels per animation frame
*/
public void setSpeed(int speed)
{
this.speed = speed;
}
/**
* Sets the frame rate.
* @param delay the number of milliseconds between animation frames
*/
public void setDelay(int delay)
{
this.delay = delay;
}
/**
* Sets the amplitude of the oscillation.
* @param amplitude the number of pixels by which to oscillate in either direction
*/
public void setAmplitude(int amplitude)
{
this.amplitude = amplitude;
}
/**
* Sets the period (inverse of frequency) of the oscillation.
* @param period the number of iterations/frames to complete one oscillation
*/
public void setPeriod(int period)
{
this.period = period;
}
/**
* Sets the fading trail effect.
* @param intensity the intensity of the trail effect. Values range from 0.0 to 1.0.
*/
public void setTrail(double intensity)
{
clearAmount = 0xFF - (int) (0xFF * normalize(intensity));
}
/**
* Sets the default target frame.
* @param target the default target
*/
public void setTarget(String target)
{
defaultTarget = (target == null) ? "_self" : new String(target);
}
/**
* Sets the text to be scrolled from a text file.
* @param path the path of the file that contains the text to be scrolled
* @param font the font to use in displaying the text
*/
public void setText(URL path, Font font)
{
String defaultText = "PhantomScroll was developed by <a href=\"mailto:um@mail.com\">Uldarico Muico Jr.</a>";
String contents = defaultText;
if (path != null)
{
try
{
DataInputStream stream = new DataInputStream(path.openConnection().getInputStream());
contents = stream.readLine();
String line;
while ((line = stream.readLine()) != null)
contents = contents + line;
stream.close();
}
catch (IOException e)
{
e.printStackTrace();
contents = defaultText;
}
}
setText(contents, font);
}
/**
* Sets the text to be scrolled. Hyperlinks and breaks may be indicated as in HTML.
* @param text the text to be scrolled
* @param font the font to use in displaying the text
*/
public void setText(String text, Font font)
{
text = text + "<br>";
Vector modifiedText = new Vector();
Vector stack = new Vector();
Vector data = new Vector();
int prevIndex = 0;
textCount = 0;
while (true)
{
int index = text.indexOf('<', prevIndex);
if (index == -1) break;
int endIndex = searchBracket(text, index);
Properties tagProps = parseTagProperties(text.substring(index + 1, endIndex));
while (modifiedText.size() <= textCount)
modifiedText.addElement(new String());
while (data.size() <= textCount)
data.addElement(new Vector());
String s = (String) modifiedText.elementAt(textCount);
modifiedText.setElementAt(s.concat(text.substring(prevIndex, index)), textCount);
String type = tagProps.getProperty("type");
if (type.equals("br"))
{
if (stack.size() > 0)
{
String tail = text.substring(endIndex + 1);
text = text.substring(0, endIndex + 1);
for (int i = 0; i < stack.size(); i++)
{
Hashtable table = (Hashtable) stack.elementAt(i);
String t = (String) table.get("type");
text = text + "</" + t + ">";
tail = ((String) table.get("tag")) + tail;
table.remove("tag");
}
text = text + "<br>" + tail;
}
else
textCount++;
}
else if (type.equals("a"))
{
URL url = parseURL(tagProps.getProperty("href"), getDocumentBase(), false);
String target = tagProps.getProperty("target");
String alt = tagProps.getProperty("alt");
Hashtable table = new Hashtable();
table.put("type", "a");
table.put("href", url);
if (target != null)
table.put("target", target);
int[] range = new int[2];
range[0] = ((String) modifiedText.elementAt(textCount)).length();
table.put("range", range);
table.put("tag", text.substring(index, endIndex + 1));
table.put("alt", (alt == null) ? url.toString() : alt);
stack.addElement(table);
}
else if (type.equals("/a"))
{
int i = stack.size() - 1;
while (i >= 0)
{
Hashtable table = (Hashtable) stack.elementAt(i);
if (table.get("type") == "a")
{
int[] range = (int[]) table.get("range");
range[1] = ((String) modifiedText.elementAt(textCount)).length() - 1;
((Vector) data.elementAt(textCount)).addElement(table);
stack.removeElementAt(i);
break;
}
i--;
}
}
prevIndex = endIndex + 1;
}
stack.removeAllElements();
FontMetrics metrics = getFontMetrics(font);
textWidths = new int[textCount];
links = new Hashtable[textCount][];
linkRanges = new int[textCount][][];
textPixels = new int[textCount][];
for (int i = 0; i < textCount; i++)
{
String t = (String) modifiedText.elementAt(i);
int[] buffer = createTextPixels(t, font, fgColor);
Dimension size = getTextSize(t, metrics);
textWidths[i] = size.width;
textHeight = size.height;
textPixels[i] = new int[textWidths[i] * textHeight];
Vector textData = (Vector) data.elementAt(i);
linkRanges[i] = new int[textData.size()][];
links[i] = new Hashtable[textData.size()];
for (int j = 0; j < textData.size(); j++)
{
links[i][j] = (Hashtable) textData.elementAt(j);
int[] indices = (int[]) links[i][j].get("range");
linkRanges[i][j] = new int[2];
linkRanges[i][j][0] = metrics.stringWidth(t.substring(0, indices[0])) + metrics.charWidth(' ');
linkRanges[i][j][1] = linkRanges[i][j][0] + metrics.stringWidth(t.substring(indices[0], indices[1] + 1));
for (int k = linkRanges[i][j][0]; k <= linkRanges[i][j][1] - 1; k++)
buffer[(metrics.getAscent() + 2) * textWidths[i] + k] |= 0xFF000000;
}
filterPixels(buffer, textWidths[i], textHeight,
0, 0, textWidths[i], textHeight,
textPixels[i], textWidths[i], textHeight, 0, 0, 0);
}
offset = width;
updateBackground();
updateForeground();
createFade();
}
/**
* Searches for a left angle bracket.
* @param s the string in which to search
* @param index the starting index into the string
* @return the index of the first matching bracket
*/
private int searchBracket(String s, int index)
{
while (true)
{
char ch = s.charAt(index);
if (ch == '>')
return index;
else if (ch == '"' || ch == '\'')
index = s.indexOf(ch, index + 1);
if (index == -1)
return -1;
index++;
}
}
/**
* Parses the contents of an HTML tag.
* @param value the text to be parsed excluding the tag delimeters
* @return the properties
*/
private Properties parseTagProperties(String value)
{
Properties properties = new Properties();
StreamTokenizer tokenizer = new StreamTokenizer(new StringBufferInputStream(value));
String previousToken = null;
tokenizer.resetSyntax();
tokenizer.wordChars(0x21, 0xFFFF);
tokenizer.quoteChar('\'');
tokenizer.quoteChar('"');
tokenizer.ordinaryChar('=');
try
{
if (tokenizer.nextToken() != tokenizer.TT_EOF)
properties.put("type", tokenizer.sval.toLowerCase());
int type = tokenizer.nextToken();
while (type != tokenizer.TT_EOF)
{
if (type == '=' && previousToken != null)
{
tokenizer.nextToken();
properties.put(previousToken.toLowerCase(), tokenizer.sval);
previousToken = null;
}
else if (type == tokenizer.TT_WORD)
previousToken = tokenizer.sval;
type = tokenizer.nextToken();
}
}
catch (IOException e)
{
e.printStackTrace();
}
return properties;
}
/**
* Creates the alpha values for those pixels near the left and right edges of the applet.
*/
private void createFade()
{
double fadeDepth = 0.8;
int fadePeriod = textHeight;
int upperX = width / 2 + 1;
int upperY = fadePeriod / 2 + 1;
fadePixels = new int[upperY][];
for (int j = 0; j < upperY; j++)
{
fadePixels[j] = new int[upperX];
double penetration = fadeDepth * (0.65 - 0.35 * Math.cos(2.0 * Math.PI * j / fadePeriod));
int i = 0;
while (i < (int) (penetration * upperX))
{
fadePixels[j][i] = (int) ((0.5 - 0.5 * Math.cos(2.0 * Math.PI / penetration * i / width)) * 0xFF);
i++;
}
while (i < upperX)
{
fadePixels[j][i] = 0xFF;
i++;
}
}
}
/**
* Computes the dimensions of text to be displayed on the screen.
* @param text the text to be displayed
* @param metrics the font metrics
* @return the size
*/
private Dimension getTextSize(String text, FontMetrics metrics)
{
Dimension textSize = new Dimension();
textSize.width = metrics.stringWidth(text) + 2 * metrics.charWidth(' ');
textSize.height += metrics.getAscent() + metrics.getDescent() + 3;
return textSize;
}
/**
* Creates the pixels with the alpha mask that represents the message to be displayed.
* @param text the string to be displayed
* @param font the font of the text
* @param color the initial color of the text
*/
private int[] createTextPixels(String text, Font font, Color color)
{
FontMetrics metrics = getFontMetrics(font);
Dimension textSize = getTextSize(text, metrics);
int size = textSize.width * textSize.height;
int lineHeight = metrics.getAscent() + metrics.getDescent();
int pixel = ((color == null) ? 0xFF7F7F7F : color.getRGB()) & 0xFFFFFF;
Image textImage = createImage(textSize.width, textSize.height);
Graphics graphics = textImage.getGraphics();
int[] values = new int[4];
graphics.setColor(new Color(0xFF000000));
graphics.fillRect(0, 0, textSize.width, textSize.height);
graphics.setColor(new Color(0xFFFFFFFF));
graphics.setFont(font);
graphics.drawString(text, (textSize.width - metrics.stringWidth(text)) / 2, metrics.getAscent());
graphics.dispose();
int[] pixels = getPixels(textImage);
for (int index = 0; index < size; index++)
{
decomposePixel(pixels[index], values);
int value = ((values[1] + values[2] + values[3]) / 3) << 24;
pixels[index] = value | pixel;
}
return pixels;
}
/**
* Parses a string representation of an URL.
* @param value the string representation
* @param defaultValue the value if the the parsing fails
* @param codeBased indicates whether the relative URL is code-based or document-based
* @return the parsed URL
*/
private URL parseURL(String value, URL defaultValue, boolean codeBased)
{
if (value == null)
return defaultValue;
try
{
if (value.indexOf(":") != -1)
return new URL(value);
else
return new URL((codeBased) ? getCodeBase() : getDocumentBase(), value);
}
catch (MalformedURLException e)
{
e.printStackTrace();
return defaultValue;
}
}
/**
* Parses a string representation of an image. The string is simply the URL of the image.
* @param value the string representation
* @param defaultValue the value if the the parsing fails
* @return the image
*/
private Image parseImage(String value, Image defaultValue)
{
if (value != null)
return getImage(parseURL(value, null, true));
else
return defaultValue;
}
/**
* Parses a string representation of an integer.
* @param value the string representation
* @param defaultValue the value if the the parsing fails
* @return the parsed integer
*/
private int parseInt(String value, int defaultValue)
{
if (value == null)
return defaultValue;
try
{
return Integer.parseInt(value);
}
catch (NumberFormatException e)
{
e.printStackTrace();
return defaultValue;
}
}
/**
* Parses a string representation of a floating-point number in the interval 0.0 and 1.0.
* @param value the string representation
* @param defaultValue the value if the the parsing fails
* @return the parsed floating-point number
*/
private double parseUnity(String value, double defaultValue)
{
if (value == null)
return defaultValue;
try
{
return normalize(Double.valueOf(value).doubleValue());
}
catch (NumberFormatException e)
{
e.printStackTrace();
return defaultValue;
}
}
/**
* Parses a string representation of a color in RGB decimal or hexadecimal format.
* @param value the string representation
* @param defaultValue the value if the the parsing fails
* @return the parsed color
*/
private Color parseColor(String value, Color defaultValue)
{
if (value == null)
return defaultValue;
try
{
if (value.indexOf(" ") != -1)
{
StringTokenizer tokenizer = new StringTokenizer(value);
int pixel = 0xFF;
while (tokenizer.hasMoreTokens())
pixel = (pixel << 8) | Integer.parseInt(tokenizer.nextToken());
return new Color(pixel);
}
else
{
int pixel = Integer.parseInt(value, 16);
return new Color(0xFF000000 | pixel);
}
}
catch (NumberFormatException e)
{
e.printStackTrace();
return defaultValue;
}
}
/**
* Retrieves the pixels of an image.
* @param image the image from which to get the pixels
* @return the pixels
*/
private int[] getPixels(Image image)
{
int width = image.getWidth(null);
int height = image.getHeight(null);
int[] pixels = new int[width * height];
PixelGrabber grabber = new PixelGrabber(image, 0, 0, width, height, pixels, 0, width);
try
{
grabber.grabPixels();
}
catch (InterruptedException e)
{
e.printStackTrace();
return null;
}
return pixels;
}
/**
* Restrains a floating-point number to the interval 0.0 to 1.0.
* @param x the number to correct
* @return the corrected number
*/
private double normalize(double x)
{
if (x < 0.0)
x = 0.0;
else if (x > 1.0)
x = 1.0;
return x;
}
/**
* Computes the effective bounds for an image region that linearly maps to another
* @param srcW the width of the source image
* @param srcH the height of the source image
* @param srcX the x coordinate of the starting pixel in the source coordinate system
* @param srcY the y coordinate of the starting pixel in the source coordinate system
* @param destW the width of the destination image
* @param destH the height of the destination image
* @param destX the x coordinate of the starting pixel in the destination coordinate system
* @param destY the x coordinate of the starting pixel in the destination coordinate system
* @param w the width of the region
* @param h the height of the region
* @return the effective bounds in the destination coordinate system
*/
private Rectangle effectiveBounds(int srcW, int srcH, int srcX, int srcY,
int destW, int destH, int destX, int destY,
int w, int h)
{
Rectangle rect = new Rectangle(srcX, srcY, w, h);
rect = rect.intersection(new Rectangle(srcW, srcH));
rect.translate(destX - srcX, destY - srcY);
return rect.intersection(new Rectangle(destW, destH));
}
/**
* Decomposes the alpha, red, green, and blue components of a pixel.
* @param pixel the pixel to decompose
* @param values the buffer to hold the components
*/
private void decomposePixel(int pixel, int[] values)
{
values[0] = (pixel >> 24) & 0xFF;
values[1] = (pixel >> 16) & 0xFF;
values[2] = (pixel >> 8) & 0xFF;
values[3] = pixel & 0xFF;
}
/**
* Applies a blur filter to a region of an image.
* @param srcPixels the source pixels
* @param srcW the width of the source image
* @param srcH the height of the source image
* @param srcX the x coordinate of the starting pixel in the source coordinate system
* @param srcY the y coordinate of the starting pixel in the source coordinate system
* @param w the width of the region
* @param h the height of the region
* @param destPixels the destination pixels
* @param destW the width of the destination image
* @param destH the height of the destination image
* @param destX the x coordinate of the starting pixel in the destination coordinate system
* @param destY the x coordinate of the starting pixel in the destination coordinate system
* @param bgValue the default background color
*/
private void filterPixels(int[] srcPixels, int srcW, int srcH,
int srcX, int srcY, int w, int h,
int[] destPixels, int destW, int destH,
int destX, int destY, int bgValue)
{
Rectangle rect = effectiveBounds(srcW, srcH, srcX, srcY, destW, destH, destX, destY, w, h);
int i;
int bufferSize = srcW * srcH;
int dx = destX - srcX;
int dy = destY - srcY;
int[] sums = new int[3];
bgValue = 0xFFFFFF | bgValue;
rect.translate(-dx, -dy);
int upperX = rect.x + rect.width + 1;
int upperY = rect.y + rect.height + 1;
for (int column = rect.x - 1; column < upperX; column++)
{
for (i = 0; i < 3; i++)
sums[i] = 0;
for (int row = rect.y - 1; row < upperY; row++)
{
int bottomIndex = (row + 2) % 3;
int srcIndex = (row + 1) * srcW + column;
sums[bottomIndex] = 0;
for (i = -1; i <= 1; i++)
{
int batchIndex = srcIndex + i;
if (batchIndex >= 0 && batchIndex < bufferSize && column + i >= 0 && column + i < srcW)
sums[bottomIndex] += ((srcPixels[batchIndex] >> 24) & 0xFF) * ((i == 0) ? 3 : 1);
}
int destColumn = column + dx;
int destRow = row + dy;
if (destColumn >= 0 && destColumn < destW && destRow >= 0 && destRow < destH)
{
int sum = 0;
int topIndex = row + 3;
int centerIndex = topIndex + 1;
bottomIndex = topIndex + 2;
for (i = topIndex; i <= bottomIndex; i++)
sum += ((i == centerIndex) ? 3 : 1) * sums[i % 3];
int pixel;
if (row >= 0 && row < srcH && column >= 0 & column < srcW)
pixel = srcPixels[row * srcW + column] & 0xFFFFFF;
else
pixel = bgValue;
destPixels[destRow * destW + destColumn] = ((sum / 25) << 24) | pixel;
}
}
}
}
/**
* Overlays a solid color onto a region of an image.
* @param pixels the pixels
* @param width the width of the image
* @param height the height of the image
* @param x the x coordinate of the starting pixel
* @param x the y coordinate of the starting pixel
* @param w the width of the region
* @param h the height of the region
*/
private void colorPixels(int[] pixels, int width, int height, int x, int y, int w, int h, int color)
{
int size = width * height;
int[] values = new int[4];
int[] cValues = new int[4];
float[] HSBValues = new float[3];
float[] HSBCValues = new float[3];
decomposePixel(color, cValues);
Color.RGBtoHSB(cValues[1], cValues[2], cValues[3], HSBCValues);
for (int j = 0; j < h; j++)
if (y + j >= 0 && y + j < height)
for (int i = 0; i < w; i++)
if (x + i >= 0 && x + i < width)
{
double intensity = Math.cos(i * Math.PI / w);
for (int k = 0; k < 4; k++)
intensity *= intensity;
intensity = 1.0 - intensity;
int amount = (int) (0xFF * intensity);
int index = (j + y) * width + i + x;
decomposePixel(pixels[index], values);
Color.RGBtoHSB(values[1], values[2], values[3], HSBValues);
float saturation = HSBValues[1];
float brightness = HSBValues[2];
values[1] = (amount * cValues[1] + (0xFF - amount) * values[1]) / 0xFF;
values[2] = (amount * cValues[2] + (0xFF - amount) * values[2]) / 0xFF;
values[3] = (amount * cValues[3] + (0xFF - amount) * values[3]) / 0xFF;
Color.RGBtoHSB(values[1], values[2], values[3], HSBValues);
HSBValues[1] = (float) (intensity * HSBCValues[1] + (1.0 - intensity) * saturation);
HSBValues[2] = (float) (0.4 * intensity * HSBCValues[2] + (1.0 - 0.4 * intensity) * brightness);
pixels[index] = (values[0] << 24)
| (0xFFFFFF & Color.HSBtoRGB(HSBValues[0], HSBValues[1], HSBValues[2]));
}
}
/**
* Overlays an image onto another. The alpha pixels determine the opacity of the images.
* @param srcPixels the source pixels
* @param srcW the width of the source image
* @param srcH the height of the source image
* @param srcX the x coordinate of the starting pixel in the source coordinate system
* @param srcY the y coordinate of the starting pixel in the source coordinate system
* @param w the width of the region
* @param h the height of the region
* @param destPixels the destination pixels
* @param destW the width of the destination image
* @param destH the height of the destination image
* @param destX the x coordinate of the starting pixel in the destination coordinate system
* @param destY the x coordinate of the starting pixel in the destination coordinate system
* @param clearing indicates that the source pixels are refreshing the canvas
* @param fading indicates that the source pixels are to be faded near the edges
*/
private void overlayPixels(int[] srcPixels, int srcW, int srcH,
int srcX, int srcY, int w, int h,
int[] destPixels, int destW, int destH,
int destX, int destY, boolean clearing, boolean fading)
{
Rectangle rect = effectiveBounds(srcW, srcH, srcX, srcY, destW, destH, destX, destY, w, h);
int dx = destX - srcX;
int dy = destY - srcY;
int i;
int j = rect.y * destW;
int k = (rect.y - dy) * srcW;
int upperI = rect.x + rect.width;
int upperJ = (rect.y + rect.height) * destW;
int fadeI;
int fadeJ = fadeIndex;
int center = width / 2;
int[] srcRGB = new int[4];
int[] destRGB = new int[4];
int[] cRGB = new int[4];
boolean incremented = fadeIncremented;
decomposePixel(activeLinkColor.getRGB(), cRGB);
while (j < upperJ)
{
if (rect.x <= center)
fadeI = rect.x;
else
fadeI = 2 * center - rect.x;
for (i = rect.x; i < upperI; i++)
{
int srcPixel = srcPixels[k + i - dx];
decomposePixel(srcPixel, srcRGB);
if (srcRGB[0] != 0)
{
int destIndex = j + i;
int destPixel = destPixels[destIndex];
if (currentLink >= 0 && !clearing
&& linkRanges[currentText][currentLink][0] <= i - destX
&& i - destX <= linkRanges[currentText][currentLink][1])
{
srcRGB[1] = cRGB[1];
srcRGB[2] = cRGB[2];
srcRGB[3] = cRGB[3];
}
if (clearing)
srcRGB[0] = clearAmount;
else if (fading)
srcRGB[0] = srcRGB[0] * fadePixels[fadeJ][fadeI] / 0xFF;
int destAmount = 0xFF - srcRGB[0];
decomposePixel(destPixel, destRGB);
destRGB[1] = (destAmount * destRGB[1] + srcRGB[0] * srcRGB[1]) / 0xFF;
destRGB[2] = (destAmount * destRGB[2] + srcRGB[0] * srcRGB[2]) / 0xFF;
destRGB[3] = (destAmount * destRGB[3] + srcRGB[0] * srcRGB[3]) / 0xFF;
destPixels[destIndex] = 0xFF000000
| (destRGB[1] << 16)
| (destRGB[2] << 8)
| destRGB[3];
}
if (i < center)
fadeI++;
else
fadeI--;
}
if (incremented)
fadeJ++;
else
fadeJ--;
if (fadeJ >= fadePixels.length)
{
fadeJ--;
incremented = false;
}
else if (fadeJ < 0)
{
fadeJ++;
incremented = true;
}
j += destW;
k += srcW;
}
}
}