/* * LineWrapManager.java 1.2 15/12/98 * * Copyright (c) 1997, 1998 by Kevin Swan. All rights reserved. * * I reserve all rights to this software. You may use it and * distribute it freely, provided you do not remove this header * and attribute partial credit to the author, Kevin Swan. * * 14 Apr 1998 Created. * Released as version 1.0. * 16 Apr 1998 Corrected a bug in setFontSize which was causing lines to * not wrap correctly. * Released as version 1.1. * 15 Dec 1998 Modified because of changes to TextScroll. Had * to recognize the new methods. * Released as version 1.2. */ /* * Commented out for unbundled distribution. * * package kevin.applets.textscroll; */ import java.util.StringTokenizer; import java.util.Vector; import java.awt.Font; import java.awt.FontMetrics; /** * This class does the line wrapping for our text. Line wrapping is * complicated in an applet such as this, because the font face and size * are dynamic. One cannot simply say, "lines are 20 characters * wide." This class actually goes through and renders the entire * text presentation off-screen. You would use it like this: * *
* ... // text is our String[] of data, width is the width of the applet.
* LineWrapManager wrapManager = new LineWrapManager (text, face, size);
* text = wrapManager.wrapForWidth (width);
*
*
* After that, text
would still contain the same data,
* but each element of the array might hold more or fewer words than it
* did before, and it might contain more or fewer elements overall.
*
*
Wrapping lines that are too long is one thing, but it must be * decided whether to bring up the next line if there is room on the * previous line. This class decides to do this. This complicates * things slightly when you want a new paragraph. There is one simple * rule to remember when composing your text file content: * *
*
* "The first newline is always ignored." ** *
This means when you want to explicitely start a new line, you must * put two carriage returns directly next to each other. Three newlines * in the text file will give you two newlines in the applet. * *
It is also important to note that directives always get their own * line in the data file. When the applet encounters a directive, it * starts a new line, no matter what. * *
This class basically provides empty implementations for all the
* methods a DirectiveManager
might call. It doesn't actually
* print anything anywhere, it just uses configuration information to
* measure lines of text, and wrap them appropriately.
*
* @version 1.2, 15 Dec 1998
* @author Kevin Swan, 013639s@dragon.acadiau.ca
*/
public class LineWrapManager extends TextScroll {
private StringTokenizer tokenizer;
private int lineNum;
private String[] data;
private Font font;
private int fontSize;
private int fontStyle;
private String fontFace;
private DirectiveManager directiveManager;
private int textInset;
/**
* Constructor. Takes the data to be wrapped, the initial font face,
* and the intial font size.
*
* @param text The String
array to have line wrapping
* performed on it.
* @param font The initial font to use.
*/
public LineWrapManager (String[] text, Font font) {
this.data = text;
this.fontFace = font.getName ();
this.fontSize = font.getSize ();
this.font = font;
this.lineNum = 0;
this.tokenizer = new StringTokenizer (this.data[lineNum], " \t\n\r", true);
this.directiveManager = new DirectiveManager (this);
}
/**
* Doesn't need to do anything in this class.
*/
public void setRightForegroundColor (String color) { }
/**
* Doesn't need to do anything in this class.
*/
public void setLeftForegroundColor (String color) { }
/**
* Doesn't need to do anything in this class.
*/
public void setRightBackgroundColor (String color) { }
/**
* Doesn't need to do anything in this class.
*/
public void setLeftBackgroundColor (String color) { }
/**
* Doesn't need to do anything in this class.
*/
public void setSpeed (String speed) { }
/**
* Doesn't need to do anything in this class.
*/
public void pause () { }
/**
* Doesn't need to do anything in this class.
*/
public void pause (String delay) { }
/**
* Ignore. We don't care about the left pane.
*
* @param face The name of the font face to use
* in the left pane.
*/
public void setLeftFontFace (String face) {
}
/**
* Note the new font face.
*
* @param face The name of the font face to use
* in the right pane.
*/
public void setRightFontFace (String face) {
this.font = new Font (face, this.fontStyle, this.fontSize);
}
/**
* Note the new font face.
*
* @param face The name of the font face to use
* in the right pane.
*/
public void setFontFace (String face) {
this.setRightFontFace (face);
}
/**
* Ignore. We don't care about the left pane.
*
* @param size The integer size to use for the font
* in the left pane.
*/
public void setLeftFontSize (String size) {
}
/**
* Note the new font size.
*
* @param size The integer size to use for the font
* in the right pane.
*/
public void setRightFontSize (String size) {
try {
this.fontSize = Integer.parseInt (size);
} catch (NumberFormatException nfe) {
this.fontSize = 10;
}
this.font = new Font (this.fontFace, this.fontStyle, this.fontSize);
}
/**
* Note the new font size.
*
* @param size The integer size to use for the font
* in the right pane.
*/
public void setFontSize (String size) {
this.setRightFontSize (size);
}
/**
* Whether we should center the text in the left
* pane or not. Ignored.
*/
public void setLeftCenter (String flag) {
}
/**
* Whether we should center the text in the right
* pane or not. Ignored.
*/
public void setRightCenter (String flag) {
}
/**
* Whether we should center the text in the right
* pane or not. Ignored.
*/
public void setCenter (String flag) {
}
/**
* Sets the bold flag for the left pane based on
* the argument. Ignore. We don't care about
* the left pane.
*
* @param flag Should be either "true"
* or "false."
*/
public void setLeftBold (String flag) {
}
/**
* Sets the bold flag for the right pane based on
* the argument.
*
* @param flag Should be either "true"
* or "false."
*/
public void setRightBold (String flag) {
if ((Boolean.valueOf (flag)).booleanValue ())
this.fontStyle = this.fontStyle | Font.BOLD;
else
this.fontStyle = this.fontStyle & ~Font.BOLD;
this.font = new Font (this.fontFace, this.fontStyle, this.fontSize);
}
/**
* Sets the bold flag for the right pane based on
* the argument.
*
* @param flag Should be either "true"
* or "false."
*/
public void setBold (String flag) {
this.setRightBold (flag);
}
/**
* Sets the italic flag for the left pane based on
* the argument. Ignore. We don't care about the
* left pane.
*
* @param flag Should be either "true"
* or "false."
*/
public void setLeftItalic (String flag) {
}
/**
* Sets the italic flag for the right pane based on
* the argument.
*
* @param flag Should be either "true"
* or "false."
*/
public void setRightItalic (String flag) {
if ((Boolean.valueOf (flag)).booleanValue ())
this.fontStyle = this.fontStyle | Font.ITALIC;
else
this.fontStyle = this.fontStyle & ~Font.ITALIC;
this.font = new Font (this.fontFace, this.fontStyle, this.fontSize);
}
/**
* Sets the italic flag for the right pane based on
* the argument.
*
* @param flag Should be either "true"
* or "false."
*/
public void setItalic (String flag) {
this.setRightItalic (flag);
}
/**
* Sets the text inset.
*
* @param size The integer size to use for the inset.
*/
public void setInset (String insetStr) {
try {
this.textInset = Integer.parseInt (insetStr);
} catch (NumberFormatException nfe) {
return;
}
}
/**
* This method actually does all the wrapping work. It takes
* an integer argument and wraps the text in the String
* array using that argument as a width.
*
* @param width The integer width of the display area to wrap to.
*
* @return A String
array of text, of which every line
* will fit in the display area, except for lines consisting
* only of a single word, which itself is too wide for the
* display area.
*/
public String[] wrapForWidth (int width) {
Vector lines = new Vector ();
String line = "";
String tok;
FontMetrics fm;
boolean newlineFlag = false;
fm = this.getToolkit ().getFontMetrics (this.font);
/*
* ALGORITHM: Read tokens by calling nextToken until it returns
* null.
*/
while (true) {
/* Read a token. */
tok = this.nextToken ();
/*
* If there are no more tokens, check to see if line contains
* valid data. If so, add it to the Vector. Break out of this
* loop.
*/
if (tok == null) {
if (line.length () > 0) {
line = line.trim ();
lines.addElement (line);
}
break;
}
/*
* If its a newline, check the flag.
* If the flag is set,
* then the user wants an actual blank line. Print the
* current line out, and leave the flag set.
* If the flag is not set, set it and continue.
*/
if (tok.equals ("\n"))
if (newlineFlag) {
line = line.trim ();
lines.addElement (line);
/*
* If they had 2 in a row, they want a new paragraph.
* Add a blank line.
*/
lines.addElement ("");
line = "";
continue;
} else {
/* This is the first newline. Set the flag, append a space. */
newlineFlag = true;
line = line.trim ();
line += " ";
continue;
}
/*
* If we get this far, then we hit a non-newline character.
* Un-set the newline flag.
*/
newlineFlag = false;
/*
* If it is a directive, start a new line and handle
* the directive. Continue.
*/
if (tok.startsWith ("^^")) {
/*
* We've hit a directive. Write whatever we've got in the
* line out now, and reset it.
*/
if (line.length () > 0) {
line = line.trim ();
lines.addElement (line);
line = "";
}
/* OK, now, get the rest of the line. */
String directive = tok;
while (true) {
tok = nextToken ();
if (tok == null)
break;
if (tok.equals ("\n"))
break;
directive += tok;
}
this.directiveManager.performDirective (directive);
fm = this.getToolkit ().getFontMetrics (this.font);
line = line.trim ();
lines.addElement (directive);
continue;
}
/*
* If the current line plus this token is too long,
* If there is no other data in "line,"
* add it anyway.
* otherwise,
* add the line to the new Vector, start a
* new line, and add this token.
*/
if (fm.stringWidth (line + tok) > (width - 2 - this.textInset)) {
if (line.length () == 0) {
lines.addElement (tok);
continue;
} else {
line = line.trim ();
lines.addElement (line);
line = tok;
}
} else
line = line.trim () + " " + tok.trim ();
}
String[] newData = new String [lines.size ()];
for (int i = 0 ; i < lines.size () ; i++)
newData [i] = (String) lines.elementAt (i);
return newData;
} /* wrapForWidth */
/**
* This method is used to tokenize the text in the array. It lets
* us treat the data in the array as a Stream.
*
* @return The next token of data. This includes whitespace, so the
* caller can look for repeated newlines.
*/
public String nextToken () {
while (!this.tokenizer.hasMoreTokens ()) {
if (lineNum == (this.data.length - 1))
return null;
this.tokenizer = new StringTokenizer (this.data [++lineNum], " \t\n\r", true);
}
return tokenizer.nextToken ();
}
/**
* Lets us "un-get" a token.
*
* @param tok The token to put back onto the tokenizer. After this,
* the next call to nextToken()
should return
* tok
.
*/
public void unGet (String tok) {
String tmp = "";
while (this.tokenizer.hasMoreTokens ())
tmp += this.tokenizer.nextToken ();
tmp = tok + tmp;
this.tokenizer = new StringTokenizer (tmp, " \t\n\r", true);
}
}