/*
*
*
* Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* 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 version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
package com.sun.midp.lcdui;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import com.sun.midp.chameleon.skins.StringItemSkin;
import com.sun.midp.i18n.Resource;
import com.sun.midp.i18n.ResourceConstants;
/**
* Static method class use to draw and size text.
*/
public class Text {
/** Character to be used as a truncation indiactor,
* for example, \u2026 which is ellipsis (...). */
private static final char truncationMark = Resource.getString(ResourceConstants.TRUNCATION_MARK).charAt(0);//'\u2026';
// the following are used in calling the getNextLine method
/** Line start. */
private static final int GNL_LINE_START = 0;
/** Line end. */
private static final int GNL_LINE_END = 1;
/** New line start. */
private static final int GNL_NEW_LINE_START = 2;
/** Screen width available. */
private static final int GNL_WIDTH = 3;
/** Screen height available. */
private static final int GNL_HEIGHT = 4;
/** Font height. */
private static final int GNL_FONT_HEIGHT = 5;
/** Line number. */
private static final int GNL_NUM_LINES = 6;
/** Text options (NORMAL, INVERT...) see below. */
private static final int GNL_OPTIONS = 7;
/** Text pixel offset. */
private static final int GNL_OFFSET = 8;
/** Width of the ellipsis in the current font. */
private static final int GNL_ELLIP_WIDTH = 9;
/** Line width in pixels. */
private static final int GNL_LINE_WIDTH = 10;
/** Number of GNL_ parameter constants. */
private static final int GNL_NUM_PARAMS = 11;
// constants to affect how text drawing is handled
// These values can be OR'd together. no error checking is performed
/** NORMAL text. */
public static final int NORMAL = 0x0;
/** INVERTED text color. */
public static final int INVERT = 0x1;
/** Draw a hyperlink for the text. */
public static final int HYPERLINK = 0x2;
/** Truncate the text and put a "..." if the text doesn't fit the bounds. */
public static final int TRUNCATE = 0x4;
// these values are stored used as setting in a TextCursor object
/**
* When a paint occurs use the cursor index to know when to
* paint the cursor.
*/
public static final int PAINT_USE_CURSOR_INDEX = 0;
/**
* When a paint occurs try to find the best value for the cursor
* index based on the x,y coordinates of the cursor.
*/
public static final int PAINT_GET_CURSOR_INDEX = 1;
/**
* Don't draw a cursor.
*/
public static final int PAINT_HIDE_CURSOR = 2;
/**
* Sets up a new inout structure used in various
* text methods.
* @param font the font to use
* @param w the available width for the text
* @param h the available height for the text
* @param options any of NORMAL | INVERT | HYPERLINK | TRUNCATE
* @param offset the first line pixel offset
* @return initialized GNL_struct
*/
public static int[] initGNL(Font font, int w, int h,
int options, int offset) {
int[] inout = new int[GNL_NUM_PARAMS];
inout[GNL_FONT_HEIGHT] = font.getHeight();
inout[GNL_WIDTH] = w;
inout[GNL_HEIGHT] = h;
inout[GNL_OPTIONS] = options;
inout[GNL_OFFSET] = offset;
inout[GNL_ELLIP_WIDTH] = font.charWidth(truncationMark);
inout[GNL_LINE_START] = 0;
inout[GNL_LINE_END] = 0;
inout[GNL_NEW_LINE_START] = 0;
inout[GNL_LINE_WIDTH] = 0;
inout[GNL_NUM_LINES] = 0;
return inout;
}
/**
* Paints the text in a single line, scrolling left or right as
* necessary to keep the cursor visible within the available
* width for the text. The offset of the text after the
* paintLine call, whether modified or not, is returned.
* <p>
* If the cursor is null, signifying an uneditable TextField is
* being painted, the text will not be scrolled left or right, and
* the returned value will always equal the <code>offset</code>
* argument passed in to this method.
*
* @param g the Graphics object to paint in
* @param str the String to paint
* @param font the font to use
* @param fgColor foreground color
* @param w the available width for the text
* @param h the available height for the text
* @param cursor TextCursor object to use for cursor placement
* @param offset the pixel offset of the text (possibly negative)
* @return the current scroll offset
*/
public static int paintLine(Graphics g, String str, Font font, int fgColor,
int w, int h, TextCursor cursor, int offset) {
if (w <= 0 ||
(cursor == null && (str == null || str.length() == 0))) {
return 0;
}
if (str == null) {
str = "";
}
g.setFont(font);
g.setColor(fgColor);
char[] text = str.toCharArray();
int fontHeight = font.getHeight();
if (cursor != null && cursor.visible == false) {
cursor = null;
}
// side-scroll distance in pixels, with default
int scrollPix = w / 2;
//
// draw a vertical cursor indicator if required
//
if (cursor != null &&
cursor.option == PAINT_USE_CURSOR_INDEX &&
cursor.index >= 0 && cursor.index <= str.length()) {
int pos = offset;
if (cursor.index > 0) {
pos += font.charsWidth(text, 0, cursor.index);
}
// IMPL_NOTE: optimize this with math instead of iteration
cursor.x = pos;
if (cursor.x >= w) {
while (cursor.x >= w) {
offset -= scrollPix;
cursor.x -= scrollPix;
}
} else {
while ((cursor.x < w / 2) && (offset < 0)) {
offset += scrollPix;
cursor.x += scrollPix;
}
}
cursor.y = fontHeight;
cursor.width = 1;
cursor.height = fontHeight;
cursor.paint(g);
cursor = null;
}
g.drawChars(text, 0, text.length, offset, h,
Graphics.BOTTOM | Graphics.LEFT);
return offset;
}
/**
* Creates a current TextInfo struct, linewraping text
* when necessary. TextInfo struct is updated when
* <code>str</code> changes, or when scrolling happens.
* This method does not do any painting, but updates
* <code>info</code> to be current for use by the
* paint routine, <code>paintText</code>...
*
* @param str the text to use
* @param font the font to use for sizing
* @param w the available width for the text
* @param h the available height for the text
* @param offset the pixel offset of the text (possibly negative)
* @param options only TRUNCATE matters here
* @param cursor text cursor object for cursor position
* @param info TextInfo structure to fill
* @return true if successful, false if there was an error
*/
// IMPL_NOTE: break into 3 simpler update methods: text, Y scroll, X scroll
public static boolean updateTextInfo(String str, Font font,
int w, int h, int offset, int options,
TextCursor cursor, TextInfo info) {
if (w <= 0 ||
(cursor == null && (str == null || str.length() == 0))) {
return false;
}
if (str == null) {
str = "";
}
char[] text = str.toCharArray();
int fontHeight = font.getHeight();
if (cursor != null && cursor.visible == false) {
cursor = null;
}
if (info.isModified) {
int[] inout = initGNL(font, w, h, options, offset);
int numLines = 0;
int height = 0;
do {
numLines++;
height += fontHeight;
info.numLines = numLines;
if (height < h) {
info.visLines = info.numLines;
}
inout[GNL_NUM_LINES] = numLines;
getNextLine(text, font, inout);
int lineStart = inout[GNL_LINE_START];
int lineEnd = inout[GNL_LINE_END];
int newLineStart = inout[GNL_NEW_LINE_START];
// IMPL_NOTE: add accessor fn to TextInfo and hide this
//
// check that we don't exceed info's capacity
// before we cache line data and expand if needed
//
if (numLines > info.lineStart.length) {
info.expand();
}
info.lineStart[numLines - 1] = lineStart;
info.lineEnd[numLines - 1] = lineEnd;
inout[GNL_LINE_START] = newLineStart;
inout[GNL_OFFSET] = 0;
offset = 0;
} while (inout[GNL_LINE_END] < text.length);
info.height = height;
}
if (info.scrollY) {
// if (lineEnd > lineStart) {
// we are given x,y coordinates and we must calculate
// the best array index to put the cursor
//
if (cursor != null &&
cursor.option == PAINT_GET_CURSOR_INDEX &&
cursor.x >= 0) {
// cursor.y == height) {
int curLine = (cursor.y / fontHeight) - 1;
int curX = curLine == 0 ? offset : 0;
int curY = cursor.y;
int bestX = curX;
int bestIndex = info.lineStart[curLine];
// take one character at a time and check its position
// against the supplied coordinates in cursor
//
int lineStart = info.lineStart[curLine];
int lineEnd = info.lineEnd[curLine];
for (int i = lineStart; i < lineEnd; i++) {
char ch = text[i];
if (Math.abs(curX - cursor.preferredX) <
Math.abs(bestX - cursor.preferredX)) {
bestIndex = i;
bestX = curX;
}
curX += font.charWidth(ch);
}
if (Math.abs(curX - cursor.preferredX) <
Math.abs(bestX - cursor.preferredX)) {
bestIndex = lineEnd;
bestX = curX;
}
cursor.index = bestIndex;
cursor.x = bestX;
// cursor.y = height;
cursor.option = PAINT_USE_CURSOR_INDEX;
info.cursorLine = curLine;
}
}
if (info.scrollX || info.isModified) {
if (cursor != null &&
cursor.option == PAINT_USE_CURSOR_INDEX) {
if (cursor.index >= info.lineStart[info.cursorLine] &&
cursor.index <= info.lineEnd[info.cursorLine]) {
// no change to info.cursorLine
} else {
// IMPL_NOTE: start at cursorLine and search before/after
// as this search is non-optimal
for (int i = 0; i < info.numLines; i++) {
// we are given an index...what line is it on?
if (cursor.index >= info.lineStart[i] &&
cursor.index <= info.lineEnd[i]) {
info.cursorLine = i;
break;
}
}
}
}
}
// check scroll position and move if needed
if (cursor != null && (info.isModified ||info.scrollX || info.scrollY)) {
if (info.numLines > info.visLines) {
if (info.cursorLine > info.topVis + info.visLines - 1) {
int diff = info.cursorLine -
(info.topVis + info.visLines - 1);
info.topVis += diff;
} else if (info.cursorLine < info.topVis) {
int diff = info.topVis - info.cursorLine;
info.topVis -= diff;
}
if (info.topVis + info.visLines > info.numLines) {
info.topVis = info.numLines - info.visLines;
}
} else {
info.topVis = 0;
}
cursor.yOffset = info.topVis * fontHeight;
}
info.scrollX = info.scrollY = info.isModified = false;
return true;
}
/**
* Paints text from a TextInfo structure.
*
* @param info the TextInfo struct
* @param g the Graphics to paint with
* @param str the text to paint
* @param font the font to use in painting the text
* @param fgColor foreground color
* @param fgHColor foreground hilight color
* @param w the available width for the text
* @param h the available height for the text
* @param offset the first line pixel offset
* @param options any of NORMAL | INVERT | HYPERLINK | TRUNCATE
* @param cursor text cursor object
*/
public static void paintText(TextInfo info, Graphics g, String str,
Font font, int fgColor, int fgHColor,
int w, int h, int offset, int options,
TextCursor cursor) {
// NOTE paint not called if TextInfo struct fails
g.setFont(font);
g.setColor(fgColor);
char[] text = str.toCharArray();
int fontHeight = font.getHeight();
if (cursor != null && cursor.visible == false) {
cursor = null;
}
int currentLine = info.topVis;
int height = currentLine * fontHeight;
int y = 0;
while (currentLine < (info.topVis + info.visLines)) {
height += fontHeight;
y += fontHeight;
g.drawChars(text, info.lineStart[currentLine],
info.lineEnd[currentLine] - info.lineStart[currentLine],
offset, y,
Graphics.BOTTOM | Graphics.LEFT);
// draw the vertical cursor indicator if needed
// update the cursor.x and cursor.y info
if (cursor != null &&
cursor.option == PAINT_USE_CURSOR_INDEX &&
cursor.index >= info.lineStart[currentLine] &&
cursor.index <= info.lineEnd[currentLine]) {
int off = offset;
if (cursor.index > info.lineStart[currentLine]) {
off += font.charsWidth(text, info.lineStart[currentLine],
cursor.index -
info.lineStart[currentLine]);
}
cursor.x = off;
cursor.y = height;
cursor.width = 1; // IMPL_NOTE: must these always be set?
cursor.height = fontHeight;
cursor.paint(g);
cursor = null;
}
offset = 0;
currentLine++;
}
}
/**
* Paints the text, linewrapping when necessary.
*
* @param g the Graphics to use to paint with
* @param str the text to paint
* @param font the font to use to paint the text
* @param fgColor foreground color
* @param fgHColor foreground highlight color
* @param w the available width for the text
* @param h the available height for the text
* @param offset the first line pixel offset
* @param options any of NORMAL | INVERT | HYPERLINK | TRUNCATE
* @param cursor text cursor object to use to draw vertical bar
* @return the width of the last line painted
*/
public static int paint(Graphics g, String str,
Font font, int fgColor, int fgHColor,
int w, int h, int offset, int options,
TextCursor cursor) {
if (w <= 0 ||
(cursor == null && (str == null || str.length() == 0))) {
return 0;
}
if (str == null) {
str = "";
}
g.setFont(font);
g.setColor(fgColor);
char[] text = str.toCharArray();
int fontHeight = font.getHeight();
if (cursor != null && cursor.visible == false) {
cursor = null;
}
int[] inout = initGNL(font, w, h, options, offset);
int numLines = 0;
int height = 0;
do {
numLines++;
height += fontHeight;
if (height > h) {
break;
}
inout[GNL_NUM_LINES] = numLines;
boolean truncate = getNextLine(text, font, inout);
int lineStart = inout[GNL_LINE_START];
int lineEnd = inout[GNL_LINE_END];
int newLineStart = inout[GNL_NEW_LINE_START];
//
// now we can get around to actually draw the text
// lineStart is the array index of the first character to
// start drawing, while lineEnd is the index just after
// the last character to draw.
//
if (lineEnd > lineStart) {
if ((options & INVERT) == INVERT) {
g.setColor(fgHColor);
} else {
g.setColor(fgColor);
}
if ((options & HYPERLINK) == HYPERLINK) {
drawHyperLink(g, offset, height, inout[GNL_LINE_WIDTH]);
}
//
// we are given x,y coordinates and we must calculate
// the best array index to put the cursor
//
if (cursor != null &&
cursor.option == PAINT_GET_CURSOR_INDEX &&
cursor.x >= 0 &&
cursor.y == height) {
int bestIndex = lineStart;
int bestX = offset;
int curX = offset;
int curY = height;
//
// draw one character at a time and check its position
// against the supplied coordinates in cursor
//
for (int i = lineStart; i < lineEnd; i++) {
char ch = text[i];
g.drawChar(ch, curX, curY,
Graphics.BOTTOM | Graphics.LEFT);
if (Math.abs(curX - cursor.preferredX) <
Math.abs(bestX - cursor.preferredX)) {
bestIndex = i;
bestX = curX;
}
curX += font.charWidth(ch);
}
if (Math.abs(curX - cursor.preferredX) <
Math.abs(bestX - cursor.preferredX)) {
bestIndex = lineEnd;
bestX = curX;
}
//
// draw the ellipsis
//
if (truncate) {
g.drawChar(truncationMark,
curX, curY,
Graphics.BOTTOM | Graphics.LEFT);
}
cursor.index = bestIndex;
cursor.x = bestX;
cursor.y = height;
cursor.option = PAINT_USE_CURSOR_INDEX;
} else {
g.drawChars(text, lineStart, lineEnd - lineStart,
offset, height,
Graphics.BOTTOM | Graphics.LEFT);
//
// draw the ellipsis
//
if (truncate) {
g.drawChar(truncationMark,
offset + font.charsWidth(
text, lineStart, lineEnd),
height,
Graphics.BOTTOM | Graphics.LEFT);
}
}
}
//
// try to draw a vertical cursor indicator
//
if (cursor != null &&
cursor.option == PAINT_USE_CURSOR_INDEX &&
cursor.index >= lineStart && cursor.index <= lineEnd) {
int off = offset;
if (cursor.index > lineStart) {
off += font.charsWidth(text, lineStart,
cursor.index - lineStart);
}
cursor.x = off;
cursor.y = height;
cursor.width = 1;
cursor.height = fontHeight;
cursor.paint(g);
cursor = null;
}
inout[GNL_LINE_START] = newLineStart;
inout[GNL_OFFSET] = 0;
offset = 0;
} while (inout[GNL_LINE_END] < text.length);
return inout[GNL_LINE_WIDTH];
}
/**
* Draws a hyperlink image.
*
* @param g the graphics to use to draw the image
* @param x the x location of the image
* @param y the y location of the image
* @param w the width of the hyperlink image
*/
public static void drawHyperLink(Graphics g, int x, int y, int w) {
if (StringItemSkin.IMAGE_LINK == null) {
// System.err.println("Hyperlink image is null");
return;
}
int linkHeight = StringItemSkin.IMAGE_LINK.getHeight();
int linkWidth = StringItemSkin.IMAGE_LINK.getWidth();
int oldClipX = g.getClipX();
int oldClipW = g.getClipWidth();
int oldClipY = g.getClipY();
int oldClipH = g.getClipHeight();
g.clipRect(x, oldClipY, w, oldClipH);
// Then, loop from the end of the string to the beginning,
// drawing the image as we go
for (int j = x + w - linkWidth, first = x - linkWidth;
j > first; j -= linkWidth) {
g.drawImage(StringItemSkin.IMAGE_LINK, j, y,
Graphics.BOTTOM | Graphics.LEFT);
}
g.setClip(oldClipX, oldClipY, oldClipW, oldClipH);
}
/**
* Gets the height in pixels and the width of the widest line in pixels
* for the given string, calculated based on the availableWidth.
* size[WIDTH] and size[HEIGHT] should be set by this method.
* @param size The array that holds Item content size and location
* in Item internal bounds coordinate system.
* @param availableWidth The width available for this Item
* @param str the string to render
* @param font the font to use to render the string
* @param offset the pixel offset for the first line
*
*/
public static void getSizeForWidth(int[] size, int availableWidth,
String str, Font font, int offset) {
// Case 0: null or empty string, no height
if (str == null || str.length() == 0 || availableWidth <= 0) {
size[HEIGHT] = 0;
size[WIDTH] = 0;
return;
}
char[] text = str.toCharArray();
int[] inout = initGNL(font, availableWidth, 0, Text.NORMAL, offset);
int numLines = 0;
int widest = 0;
boolean widthFound = false;
do {
numLines++;
inout[GNL_NUM_LINES] = numLines;
getNextLine(text, font, inout);
if (!widthFound) {
// a long line with no spaces
if (inout[GNL_LINE_WIDTH] > availableWidth && offset == 0) {
widest = availableWidth;
widthFound = true;
} else if (inout[GNL_LINE_WIDTH] > widest) {
widest = inout[GNL_LINE_WIDTH];
}
}
inout[GNL_LINE_START] = inout[GNL_NEW_LINE_START];
inout[GNL_OFFSET] = 0;
} while (inout[GNL_LINE_END] < text.length);
size[WIDTH] = widest;
size[HEIGHT] = font.getHeight() * numLines;
// return values in size[]
}
/**
* Gets the height in pixels to render the given string.
* @param str the string to render
* @param font the font to use to render the string
* @param w the available width for the string
* @param offset the pixel offset for the first line
* @return the height in pixels required to render this string completely
*/
// IMPL_NOTE - could remove and use getSizeForWidth()
public static int getHeightForWidth(String str, Font font,
int w, int offset) {
int[] tmpSize = new int[] {0, 0, 0, 0};
getSizeForWidth(tmpSize, w, str, font, offset);
return tmpSize[HEIGHT];
}
/**
* Utility method to retrieve the length of the longest line of the
* text given the width. this may not necessarily be the entire
* string if there are line breaks or word wraps.
*
* @param str the String to use.
* @param offset a pixel offset for the first line
* @param width the available width for the text
* @param font the font to render the text in
* @return the length of the longest line given the width
*/
// IMPL_NOTE - could remove and use getSizeForWidth()
public static int getWidestLineWidth(String str, int offset,
int width, Font font) {
int[] tmpSize = new int[] {0, 0, 0, 0};
getSizeForWidth(tmpSize, width, str, font, offset);
return tmpSize[WIDTH];
}
/**
* Calculates the starting and ending points for a new line of
* text given the font and input parameters. Beware of the
* multiple returns statements within the body.
*
* @param text text to process. this must not be null
* @param font font to use for width information
* @param inout an array of in/out parameters, the GNL_ constants
* define the meaning of each element;
* this array implements a structure that keeps data
* between invocations of getNextLine.
* @return true if the text had to be truncated, false otherwise
*/
private static boolean getNextLine(char[] text, Font font, int[] inout) {
//
// this inner loop will set lineEnd and newLineStart to
// the proper values so that a line is broken correctly
//
int curLoc = inout[GNL_LINE_START];
boolean foundBreak = false;
int leftWidth = 0;
inout[GNL_LINE_WIDTH] = 0;
int prevLineWidth = 0;
int curLineWidth = 0;
while (curLoc < text.length) {
//
// a newLine forces a break and immediately terminates
// the loop
//
// a space will be remembered as a possible place to break
//
if (text[curLoc] == '\n') {
inout[GNL_LINE_END] = curLoc;
inout[GNL_NEW_LINE_START] = curLoc + 1;
inout[GNL_LINE_WIDTH] = prevLineWidth;
return
( ((inout[GNL_OPTIONS] & TRUNCATE) == TRUNCATE)
&& ((inout[GNL_NUM_LINES] + 1) * inout[GNL_FONT_HEIGHT]
> inout[GNL_HEIGHT])
);
} else if (text[curLoc] == ' ') {
inout[GNL_LINE_END] = curLoc;
inout[GNL_NEW_LINE_START] = curLoc + 1;
inout[GNL_LINE_WIDTH] = prevLineWidth;
foundBreak = true;
}
//
// if the text is longer than one line then we
// cut the word at a word boundary if possible,
// otherwise the word is broken.
//
curLineWidth = prevLineWidth + font.charWidth(text[curLoc]);
// check up the mode is "truncate" and we reached the end of
// the last line that we can put into the specifed rectangle area
// (inout[GNL_WIDTH] x inout[GNL_HEIGHT])
if (((inout[GNL_OPTIONS] & TRUNCATE) == TRUNCATE)
&& ((inout[GNL_NUM_LINES] + 1) * inout[GNL_FONT_HEIGHT]
> inout[GNL_HEIGHT])
&& (inout[GNL_OFFSET] + curLineWidth + inout[GNL_ELLIP_WIDTH]
> inout[GNL_WIDTH])) {
leftWidth = font.charsWidth(text, curLoc + 1,
text.length - curLoc - 1);
//
// we are on the last line and at the point where
// we will need to put an ellipsis if we can't fit
// the rest of the line
//
// if the rest of the line will fit, then don't
// put an ellipsis
//
if (inout[GNL_OFFSET] + curLineWidth + leftWidth
> inout[GNL_WIDTH]) {
prevLineWidth += inout[GNL_ELLIP_WIDTH];
inout[GNL_LINE_END] = curLoc;
inout[GNL_NEW_LINE_START] = curLoc;
inout[GNL_LINE_WIDTH] = prevLineWidth;
return true;
} else {
curLineWidth += leftWidth;
inout[GNL_LINE_END] = text.length;
inout[GNL_NEW_LINE_START] = text.length;
inout[GNL_LINE_WIDTH] = curLineWidth;
return false;
}
// reached the end of the line
} else if (inout[GNL_OFFSET] + curLineWidth > inout[GNL_WIDTH]) {
if (!foundBreak) {
if (inout[GNL_OFFSET] > 0) {
// move to the next line which will have 0 offset
inout[GNL_LINE_END] = inout[GNL_LINE_START];
inout[GNL_NEW_LINE_START] = inout[GNL_LINE_START];
inout[GNL_LINE_WIDTH] = 0;
} else {
// the line is too long and we need to break it
inout[GNL_LINE_END] = curLoc;
inout[GNL_NEW_LINE_START] = curLoc;
inout[GNL_LINE_WIDTH] = prevLineWidth;
}
}
return false;
}
// go to next character
curLoc++;
prevLineWidth = curLineWidth;
} // while end
// we reach this code only if we reach the end of the text
inout[GNL_LINE_END] = text.length;
inout[GNL_NEW_LINE_START] = text.length;
inout[GNL_LINE_WIDTH] = curLineWidth;
return false;
}
/**
* Utility method to calculate the width and height in which 2
* strings can fit given the strings, fonts and maximum width
* in which those strings should fit. Returned width is either
* the passed in width or a smaller one.
* The offset in pixels for the first string is 0, second string is
* laid out right after the first one with padding in between
* equal to the passed in value.
*
* The width in which both strings would fit given the maximum
* is returned in size[WIDTH]. The height in which both strings
* would fit is returned in size[HEIGHT];
*
* @param size The array that returns contents size
* @param firstStr the first string to use.
* @param secondStr the first string to use.
* @param width the available width for the text
* @param firstFont the font to render the first string in
* @param secondFont the font to render the second string in
* @param pad the horizontal padding that should be used between strings
*/
public static void getTwoStringsSize(int[] size, String firstStr,
String secondStr, Font firstFont,
Font secondFont, int width, int pad)
{
if (((firstStr == null || firstStr.length() == 0) &&
(secondStr == null || secondStr.length() == 0)) ||
(width <= 0)) {
size[WIDTH] = size[HEIGHT] = 0;
return;
}
int[] inout = new int[GNL_NUM_PARAMS];
char[] text;
int offset = 0;
int widest = 0;
int numLines = 0;
int height = 0;
int fontHeight = 0;
if (firstStr != null && firstStr.length() > 0) {
text = firstStr.toCharArray();
fontHeight = firstFont.getHeight();
inout = initGNL(firstFont, width, 0, Text.NORMAL, 0);
do {
numLines++;
height += fontHeight;
inout[GNL_NUM_LINES] = numLines;
getNextLine(text, firstFont, inout);
if (inout[GNL_LINE_WIDTH] > widest) {
widest = inout[GNL_LINE_WIDTH];
}
inout[GNL_LINE_START] = inout[GNL_NEW_LINE_START];
} while (inout[GNL_LINE_END] < firstStr.length());
offset = inout[GNL_LINE_WIDTH];
if (secondStr == null || secondStr.length() == 0) {
// last \n in the two strings should be ignored
if (firstStr.charAt(firstStr.length() - 1) == '\n') {
height -= fontHeight;
}
size[HEIGHT] = height;
size[WIDTH] = widest;
return;
}
}
// Second string is not null and it is not empty
if (secondStr != null && secondStr.length() > 0) {
if (offset > 0) {
offset += pad;
}
text = secondStr.toCharArray();
fontHeight = secondFont.getHeight();
// Line that has the end of the first string and the beginning
// of the second one is a special one;
// We have to make sure that it is not counted twice and that
// the right font height is being added (the max of the two)
if (numLines > 0) {
numLines--;
if (inout[GNL_FONT_HEIGHT] > fontHeight) {
height -= fontHeight;
} else {
height -= inout[GNL_FONT_HEIGHT];
}
}
inout = initGNL(secondFont, width, 0, Text.NORMAL, offset);
do {
numLines++;
height += fontHeight;
inout[GNL_NUM_LINES] = numLines;
getNextLine(text, secondFont, inout);
if (inout[GNL_OFFSET] + inout[GNL_LINE_WIDTH] > widest) {
widest = inout[GNL_OFFSET] + inout[GNL_LINE_WIDTH];
}
inout[GNL_LINE_START] = inout[GNL_NEW_LINE_START];
inout[GNL_OFFSET] = 0;
} while (inout[GNL_LINE_END] < secondStr.length());
// last \n should be ignored
if (secondStr.charAt(secondStr.length() - 1) == '\n') {
height -= fontHeight;
}
}
size[WIDTH] = widest;
size[HEIGHT] = height;
return;
}
// IMPL_NOTE: remove these - there must be a common place to get them
/** Used as an index into the size[], for the x. */
public final static int X = 0;
/** Used as an index into the size[], for the y. */
public final static int Y = 1;
/** Used as an index into the size[], for the width. */
public final static int WIDTH = 2;
/** Used as an index into the size[], for the height. */
public final static int HEIGHT = 3;
/**
*
*
* @param g the Graphics to paint with
* @param text the string to be painted
* @param font the font to be used
* @param fgColor foreground text color
* @param shdColor shadow color
* @param shdAlign shadow alignment
* @param titlew width
*/
public static void drawTruncStringShadowed(Graphics g, String text, Font font,
int fgColor, int shdColor, int shdAlign,
int titlew) {
int dx=1, dy=1;
// draw the shadow
if (shdColor != fgColor) {
switch (shdAlign) {
case (Graphics.TOP | Graphics.LEFT):
dx=-1;
dy=-1;
break;
case (Graphics.TOP | Graphics.RIGHT):
dx=1;
dy=-1;
break;
case (Graphics.BOTTOM | Graphics.LEFT):
dx=-1;
dy=1;
break;
case (Graphics.BOTTOM | Graphics.RIGHT):
default:
dx=1;
dy=1;
break;
}
g.translate(dx, dy);
drawTruncString(g, text, font,
shdColor, titlew);
/* if we wanted multi-line text output, we would use this:
paint(g, text, font,
shdColor, 0,
titlew, titleh, 0,
TRUNCATE, null);
*/
g.translate(-dx, -dy);
}
// now draw the text whose shadow we have drawn above
drawTruncString(g, text, font,
fgColor, titlew);
/* if we wanted multi-line text output, we would use this:
paint(g, text, font,
fgColor, 0,
titlew, titleh, 0,
TRUNCATE, null);
*/
}
/**
* Given a string, determine the length of a substring that can be drawn
* within the current clipping area.
* If the whole string fits into the clip area,
* return the length of the string.
* Else, return the length of a substring (starting from the beginning
* of the original string) that can be drawn within the current clipping
* area before the truncation indicator.
* The truncation indicator, typically, ellipsis, is not included into
* the returned length.
*
* @param g the Graphics to paint with
* @param str the string to be painted
* @param width the available width, including room
* for the truncation indicator
* @return either the length of str (if it fits into the clip area),
* or the length of the substring that can fit into the clip area
* (not including the truncation mark)
*/
public static int canDrawStringPart(Graphics g, String str,
int width) {
if (width < 0) {
return 0;
}
final Font font = g.getFont();
final int stringWidth = font.stringWidth(str);
if (width >= stringWidth) {
return str.length();
}
final int widthForTruncatedText = width - font.charWidth(truncationMark);
int availableLength;
for (availableLength = str.length() - 1 ;
font.substringWidth(str,0,availableLength) > widthForTruncatedText;
availableLength-- ) {};
return availableLength;
}
/**
* Draw the string within the specified width.
* If the string does not fit in the available width,
* it is truncated at the end,
* and a truncation indicator is displayed (usually,
* an ellipsis, but this can be changed).
* Use Graphics.translate(x,y) to specify the anchor point location
* (the alignment will be TOP|LEFT relative to 0,0).
*
* @param g the Graphics to paint with
* @param str the string to be painted
* @param font the font to be used
* @param fgColor the color to paint with
* @param width the width available for painting
*/
public static void drawTruncString(Graphics g, String str,
Font font, int fgColor, int width) {
g.setFont(font);
g.setColor(fgColor);
int lengthThatCanBeShown = canDrawStringPart(g, str, width);
if (lengthThatCanBeShown == str.length()) {
g.drawString(str, 0, 0, Graphics.TOP | Graphics.LEFT);
} else {
String s = str.substring(0,lengthThatCanBeShown) + truncationMark;
g.drawString(s, 0, 0, Graphics.TOP | Graphics.LEFT);
}
}
}
|