/* * 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); } }