/**************************************************************************
* Copyright (c) 2001, 2002, 2003 by Acunia N.V. All rights reserved. *
* *
* This software is copyrighted by and is the sole property of Acunia N.V. *
* and its licensors, if any. All rights, title, ownership, or other *
* interests in the software remain the property of Acunia N.V. and its *
* licensors, if any. *
* *
* This software may only be used in accordance with the corresponding *
* license agreement. Any unauthorized use, duplication, transmission, *
* distribution or disclosure of this software is expressly forbidden. *
* *
* This Copyright notice may not be removed or modified without prior *
* written consent of Acunia N.V. *
* *
* Acunia N.V. reserves the right to modify this software without notice. *
* *
* Acunia N.V. *
* Philips-site 5, box 3 info@acunia.com *
* 3001 Leuven http://www.acunia.com *
* Belgium - EUROPE *
**************************************************************************/
package com.acunia.wonka.rudolph.peers;
import java.awt.*;
/*
** TextArea Painter for line-wrapped texts:
** => features:
** The text stays in the horizontal area of the viewport, but is 'wrapped' to fit the viewport: lines longer then the viewport
** are split into parts of several words that do fit. Note that this implies that the actual number of lines in the painter
** can be bigger then the number of lines of the original text
** => Programming:
** - setImage just stores the COMPLETE text in the buffer, including the line break characters. textlineEnd[] now depends
** on the screen width and will be calculated in the calculateVisibleLineparts algotithm just as well
** - calculateVisibleLineparts is written to calculate the lineparts that fit in the viewport (as described above)
** - rewriting the two setScreenPosition() functions to calculate the position from a buffer including the end marks
*/
public class TextAreaPainter_WrappingVarFont extends TextAreaPainter {
/*
** Constructor
*/
public TextAreaPainter_WrappingVarFont(Font textfont, FontMetrics metrics, String text, Dimension size, Color[] colors) {
super(textfont,metrics, text, size,colors);
}
/*
** Store the new text in buffer. Store the full text including the '/n' endline marks and then call calculateVisibleLineparts
*/
public void setImage(String text) {
//initialise text buffer we include the breaks for easyer line calculation
textBuffer = new char[text.length()];
//fill the buffer (the whole buffer and nothing but the buffer...)
text.getChars(0,textBuffer.length,textBuffer,0);
//calculate the lines
if(viewport.width>0) {
//build new visible text arrays
calculateVisibleLineparts();
}
else {
//by default set end buffer to int[0] so getLines() returns a value
textlineEnd = new int[0];
}
}
/*
** calculate lines, startChars and StopChars for given text, and viewport
** with viewport.width in characters, the words width, is simply the number of characters in that word, the space width becomes 1(char)
** Note that the actual number of pieces will be bigger then the number of text lines
*/
protected void calculateVisibleLineparts() {
//security
if(textBuffer.length<=0 || viewport.width<=0) {
textlineEnd = new int[0];
viewTextOffset = new int[0];
viewTextLength = new int[0];
return;
}
// ALGORITHM:
// For simplicity, we use a tree-step algorithm:
// STEP ONE: we break the text into words, storing each word in a temporary array. As we're using a char[] buffer and start-stop-position
// arrays, we'll 'store' the words as a series of 3 int-arrays: wordstart and wordstop position, wordlength in pixels
// and one boolean[] newline
// STEP TWO: we'll re-calculate the length of line through its subsequent word length, splitting the line in the middle every time the
// length exceeds the viewport length (splitting is simply done by inserting a new 'true' in the newline buffer for every new line)
// STEP THREE: we assing a new pair of viewTextOffset/viewTextLength arrays and fill them with the lines
// STEP ONE:
// calculate number of words == number of space characters + number of lines
int i; //we'll use the for(int i==.. ;i<... ;i++) for-next loop so often, we can just use a common i
int words=1;
for(i=0;i<textBuffer.length; i++) {
if(textBuffer[i]==' ' || textBuffer[i]=='\n'
|| textBuffer[i]=='.' || textBuffer[i]==',' || textBuffer[i]=='?' || textBuffer[i]==':' || textBuffer[i]==';')
words++;
}
// build arrays for word start, stop and newline
int[] wordstart = new int[words];
int[] wordlen = new int[words]; //length in characters
int[] wordwidth = new int[words]; // width in pixels
char[] startchar = new char[words]; //true if starts on a new line
// fill arrays with words
int line=0;
//first line (length & pixel width will be calculated out of next line start)
wordstart[0]=0;
startchar[0]='\n';// first line starts on new line
//all lines in between
for(i=1; i<(textBuffer.length-1); i++) {
if(textBuffer[i]=='\n' || textBuffer[i]==' ' || textBuffer[i]=='.' || textBuffer[i]==',' || textBuffer[i]=='?' || textBuffer[i]==':' || textBuffer[i]==';') {
if(textBuffer[i]=='\n' || textBuffer[i]==' ' ) {
// a space or endchar terminated word stops on the cpace or endchar and does not include that char
wordlen[line] = i-wordstart[line];
}
else {
// a word ending on a lexigraphical sign includes that sign
wordlen[line] = i-wordstart[line]+1;
}
// word width in pixels
wordwidth[line]= painterMetrics.charsWidth(textBuffer,wordstart[line],wordlen[line]);
// start for next line
line++;
wordstart[line]=i+1; //we start at the next character
startchar[line] = textBuffer[i]; // we store the ending char of last line to remember if we should start with a new line or a new space
}
}
//last line length
wordlen[line]= textBuffer.length-wordstart[line];
wordwidth[line]=painterMetrics.charsWidth(textBuffer,wordstart[line],wordlen[line]);
//STEP TWO:
// add <newline=true; > break commands whenever the width of the chars up to now exceeds the viewport width
//first simplification: A word longer then the current viewport will always start on a new line and occupy the whole line
//so the word after them has to start on a new line just as well
int last = words-1;
for(i=0; i<last; i++) {
if(wordwidth[i]>=viewport.width) {
startchar[i]='\n';
wordlen[i]= RudolphPeer.getChars(viewport.width, wordstart[i], textBuffer, painterMetrics) -wordstart[i];
wordwidth[i]=viewport.width;
startchar[i+1]='\n';
}
}
if(wordwidth[i]>=viewport.width) {
startchar[last]='\n';
wordlen[last]= RudolphPeer.getChars(viewport.width, wordstart[last], textBuffer, painterMetrics) -wordstart[last];
wordwidth[last]=viewport.width;
}
line=1;
int curwidth=wordwidth[0];
int spacechar=painterMetrics.charWidth(' ');
for(i=1; i<words; i++) {
if(startchar[i]=='\n') {
// the word already starts at new line, so reset the linewidth counter and increase the line counter
curwidth=wordwidth[i];
line++;
}
else if (startchar[i]==' ') {
// the word starts with a space, so look if we can include space and word length into the current line width
curwidth+=wordwidth[i]+spacechar;
if(curwidth>viewport.width) {
// we can't, so start on a new line
startchar[i]='\n';
curwidth=wordwidth[i];
line++;
}
}
else
{
// the word starts with lexigraphical sign, so look if we can include word length into the current line width
curwidth+=wordwidth[i];
if(curwidth>viewport.width) {
// we can't, so start on a new line
startchar[i]='\n';
curwidth=wordwidth[i];
line++;
}
}
}
//STEP THREE:
// Copy the line start and stop into the viewTextOffset[]/viewTextLength[] buffers
textlineEnd = new int[line];
viewTextOffset = new int[line];
viewTextLength = new int[line];
line=0;
viewTextOffset[0]=wordstart[0];
for(i=1; i<words; i++) {
if(startchar[i]=='\n') {
//new line starts here, so terminate previous line by end of previous word
textlineEnd[line]=wordstart[i-1]+wordlen[i-1];
viewTextLength[line]=textlineEnd[line] - viewTextOffset[line];
// add a new line starting with the new word position
line++;
viewTextOffset[line]=wordstart[i];
}
}
//last line, if the line ends on a final newline sign, skip that sign
textlineEnd[line]=(textBuffer[textBuffer.length-1]=='\n')?textBuffer.length-1:textBuffer.length;
viewTextLength[line]=textlineEnd[line]-viewTextOffset[line];
}
/*
** screen position to position in displayed text and vice versa. As the text buffer now includes the /n endline chars,
** there is no difference between buffer position and TextAreaPainter text string position
** => pointdata[0] : position of the selected character in the original text
** => pointdata[1] : line of the selected character on the screen
** => pointdata[2] : horizontal offset in pixels (or offset calculation value) of the selected character from the left side of the screen
** => pointdata[3] : position of the selected character in the TextPainter's text buffer (the text without the \n line breaks)
*/
public void setScreenPosition(int x, int y, int[] pointdata) {
//security
if(viewport.width<=0 || viewport.height<=0 ||textlineEnd.length<=0 ) {
return;
}
// no of lines on screen, with offset
pointdata[1] = RudolphTextAreaPeer.getLines(y, painterMetrics) + lineOffset;
if(pointdata[1]>=textlineEnd.length) {
// deeper then last line
pointdata[1]= textlineEnd.length-1;
}
// Calculate buffer position by means of RudolphPeer function
x= RudolphTextAreaPeer.getTextPos(x); // adjust to painter margins
pointdata[3]= RudolphPeer.getChars(RudolphTextAreaPeer.getTextPos(x), viewTextOffset[pointdata[1]], textBuffer,painterMetrics);
if(pointdata[3]>textlineEnd[pointdata[1]]) {
//passed end of line
pointdata[3]=textlineEnd[pointdata[1]];
}
// (in wrapping textarea: textlineEnd[i]=viewTextOffset[i]+viewTextLengt[i] = last VISIBLE char of line)
if(pointdata[3]>textlineEnd[pointdata[1]]) {
// If passed the end of line, adjust to the end
pointdata[3]=textlineEnd[pointdata[1]];
}
// calculate the horizontal pos back from the distance between total chars and end of last line using FontMetrics.charWidth
pointdata[2]= painterMetrics.charsWidth(textBuffer,viewTextOffset[pointdata[1]], pointdata[3]-viewTextOffset[pointdata[1]]);
// as line wrapping buffer INCLUDES \n line breaks, text string position = buffer position
pointdata[0] = pointdata[3];
}
/*
** position in text to screen position line + screen offset(pixels)
*/
public void setScreenPosition(int pos, int[] pointdata) {
//security
if(viewport.width<=0 || viewport.height<=0 ||textlineEnd.length<=0 ) {
return;
}
if(pos<0) {
//no selection
pointdata[0]= 0;
pointdata[1]= 0;
pointdata[2]= 0;
pointdata[3]= 0;
}
else if( pos >=textBuffer.length) {
//passed the end of buffer
pointdata[0]=textBuffer.length;
pointdata[1]=textlineEnd.length-1;
pointdata[2]=painterMetrics.charsWidth(textBuffer,viewTextOffset[pointdata[1]],viewTextLength[pointdata[1]]);
pointdata[3]=textBuffer.length;
}
else {
// in range
pointdata[0] = pos;
// calculate line by comparing to line-end lengths
pointdata[1] = 0;
while(pos>(viewTextOffset[pointdata[1]]+viewTextLength[pointdata[1]])) {
pointdata[1]++;
}
pointdata[2]= painterMetrics.charsWidth(textBuffer,viewTextOffset[pointdata[1]],pos-viewTextOffset[pointdata[1]]);
pointdata[3] = pos;
}
}
/*
** char position in line, offset to pointdata :
** pointData[1]: line and pointData[2] offset(in chars) are given.
** Up to us to calculate the corresponding text position and buffer position
** special case: when offset== -1 calculate the end of the line
** as all lines smaller then screen width, we know that the TOTAL length of a line equals viewTextLength[line]
*/
public void setScreenPositionLine(int line, int offset, int[] pointdata) {
if(line<0) {
// set all to beginning of text (first line, first pos)
pointdata[0]=0;
pointdata[1]=0;
pointdata[2]=0;
pointdata[3]=0;
}
else if(line>=textlineEnd.length) {
// set all to end of text
pointdata[0]=textBuffer.length;
pointdata[1]=textlineEnd.length-1;
pointdata[2]=(pointdata[1]>0)? painterMetrics.charsWidth(textBuffer, textlineEnd[pointdata[1]-1]+1, viewTextLength[pointdata[1]]):
painterMetrics.charsWidth(textBuffer, 0, textBuffer.length);
pointdata[3]=pointdata[0];
}
else if(offset<0) {
pointdata[0]=textlineEnd[line];
pointdata[1]=line;
pointdata[2]=(line>0)? painterMetrics.charsWidth(textBuffer, textlineEnd[line-1]+1, viewTextLength[line]):
painterMetrics.charsWidth(textBuffer, 0, textlineEnd[line]);
pointdata[3]=pointdata[0];
}
else if(offset==0) {
//speedy form, no offset in pixels means no offset in chars
pointdata[0]=(line>0)?textlineEnd[line-1]+1 : 0;
pointdata[1]=line;
pointdata[2]=0;
pointdata[3]=pointdata[0];
}
else {
pointdata[1]=line;
pointdata[0]=(line>0)?RudolphPeer.getChars(offset, textlineEnd[line-1]+1, textBuffer,painterMetrics):
RudolphPeer.getChars(offset, 0, textBuffer,painterMetrics);
if(pointdata[0]>textlineEnd[line]) {
pointdata[0]=textlineEnd[line];
pointdata[2]=(line>0)? painterMetrics.charsWidth(textBuffer, textlineEnd[line-1]+1, viewTextLength[line]):
painterMetrics.charsWidth(textBuffer, 0, textlineEnd[line]);
}
else {
pointdata[2]=offset;
}
pointdata[3]=pointdata[0];
}
}
/*
** paint
*/
public void paint (int width, int height, int cursorline, int cursoroffset, int cursorscreenpos, Graphics g){
RudolphTextAreaPeer.paintTextArea( 0, 0, width, height,
textBuffer, viewTextOffset, viewTextLength, lineOffset,
cursorline, cursorscreenpos,
painterFont, painterMetrics, textColors, g, drawCursor);
}
public void paint(int width, int height, int startline, int startbufferoffset, int starthorizontaloffset,
int stopline, int stopbufferoffset, int stophorizontaloffset,Graphics g) {
RudolphTextAreaPeer.paintTextArea( 0,0,width,height,
textBuffer,viewTextOffset,viewTextLength,lineOffset,
startline, startbufferoffset, starthorizontaloffset,
stopline, stopbufferoffset, stophorizontaloffset,
painterFont, painterMetrics, textColors, g, drawCursor);
}
}