blob: dbb9ef9c22ce05b2a6d293e376a2c9711d4fe4b7 [file] [log] [blame]
package com.jme3.font;
import com.jme3.font.BitmapFont.Align;
import com.jme3.font.BitmapFont.VAlign;
import com.jme3.font.ColorTags.Range;
import com.jme3.math.ColorRGBA;
import java.util.LinkedList;
/**
* Manage and align LetterQuads
* @author YongHoon
*/
class Letters {
private final LetterQuad head;
private final LetterQuad tail;
private final BitmapFont font;
private LetterQuad current;
private StringBlock block;
private float totalWidth;
private float totalHeight;
private ColorTags colorTags = new ColorTags();
private ColorRGBA baseColor = null;
Letters(BitmapFont font, StringBlock bound, boolean rightToLeft) {
final String text = bound.getText();
this.block = bound;
this.font = font;
head = new LetterQuad(font, rightToLeft);
tail = new LetterQuad(font, rightToLeft);
setText(text);
}
void setText(final String text) {
colorTags.setText(text);
String plainText = colorTags.getPlainText();
head.setNext(tail);
tail.setPrevious(head);
current = head;
if (text != null && plainText.length() > 0) {
LetterQuad l = head;
for (int i = 0; i < plainText.length(); i++) {
l = l.addNextCharacter(plainText.charAt(i));
if (baseColor != null) {
// Give the letter a default color if
// one has been provided.
l.setColor( baseColor );
}
}
}
LinkedList<Range> ranges = colorTags.getTags();
if (!ranges.isEmpty()) {
for (int i = 0; i < ranges.size()-1; i++) {
Range start = ranges.get(i);
Range end = ranges.get(i+1);
setColor(start.start, end.start, start.color);
}
Range end = ranges.getLast();
setColor(end.start, plainText.length(), end.color);
}
invalidate();
}
LetterQuad getHead() {
return head;
}
LetterQuad getTail() {
return tail;
}
void update() {
LetterQuad l = head;
int lineCount = 1;
BitmapCharacter ellipsis = font.getCharSet().getCharacter(block.getEllipsisChar());
float ellipsisWidth = ellipsis!=null? ellipsis.getWidth()*getScale(): 0;
while (!l.isTail()) {
if (l.isInvalid()) {
l.update(block);
if (l.isInvalid(block)) {
switch (block.getLineWrapMode()) {
case Character:
lineWrap(l);
lineCount++;
break;
case Word:
if (!l.isBlank()) {
// search last blank character before this word
LetterQuad blank = l;
while (!blank.isBlank()) {
if (blank.isLineStart() || blank.isHead()) {
lineWrap(l);
lineCount++;
blank = null;
break;
}
blank = blank.getPrevious();
}
if (blank != null) {
blank.setEndOfLine();
lineCount++;
while (blank != l) {
blank = blank.getNext();
blank.invalidate();
blank.update(block);
}
}
}
break;
case NoWrap:
// search last blank character before this word
LetterQuad cursor = l.getPrevious();
while (cursor.isInvalid(block, ellipsisWidth) && !cursor.isLineStart()) {
cursor = cursor.getPrevious();
}
cursor.setBitmapChar(ellipsis);
cursor.update(block);
cursor = cursor.getNext();
while (!cursor.isTail() && !cursor.isLineFeed()) {
cursor.setBitmapChar(null);
cursor.update(block);
cursor = cursor.getNext();
}
break;
}
}
} else if (current.isInvalid(block)) {
invalidate(current);
}
if (l.isEndOfLine()) {
lineCount++;
}
l = l.getNext();
}
align();
block.setLineCount(lineCount);
rewind();
}
private void align() {
final Align alignment = block.getAlignment();
final VAlign valignment = block.getVerticalAlignment();
if (block.getTextBox() == null || (alignment == Align.Left && valignment == VAlign.Top))
return;
LetterQuad cursor = tail.getPrevious();
cursor.setEndOfLine();
final float width = block.getTextBox().width;
final float height = block.getTextBox().height;
float lineWidth = 0;
float gapX = 0;
float gapY = 0;
validateSize();
if (totalHeight < height) { // align vertically only for no overflow
switch (valignment) {
case Top:
gapY = 0;
break;
case Center:
gapY = (height-totalHeight)*0.5f;
break;
case Bottom:
gapY = height-totalHeight;
break;
}
}
while (!cursor.isHead()) {
if (cursor.isEndOfLine()) {
lineWidth = cursor.getX1()-block.getTextBox().x;
if (alignment == Align.Center) {
gapX = (width-lineWidth)/2;
} else if (alignment == Align.Right) {
gapX = width-lineWidth;
} else {
gapX = 0;
}
}
cursor.setAlignment(gapX, gapY);
cursor = cursor.getPrevious();
}
}
private void lineWrap(LetterQuad l) {
if (l.isHead() || l.isBlank())
return;
l.getPrevious().setEndOfLine();
l.invalidate();
l.update(block); // TODO: update from l
}
float getCharacterX0() {
return current.getX0();
}
float getCharacterY0() {
return current.getY0();
}
float getCharacterX1() {
return current.getX1();
}
float getCharacterY1() {
return current.getY1();
}
float getCharacterAlignX() {
return current.getAlignX();
}
float getCharacterAlignY() {
return current.getAlignY();
}
float getCharacterWidth() {
return current.getWidth();
}
float getCharacterHeight() {
return current.getHeight();
}
public boolean nextCharacter() {
if (current.isTail())
return false;
current = current.getNext();
return true;
}
public int getCharacterSetPage() {
return current.getBitmapChar().getPage();
}
public LetterQuad getQuad() {
return current;
}
public void rewind() {
current = head;
}
public void invalidate() {
invalidate(head);
}
public void invalidate(LetterQuad cursor) {
totalWidth = -1;
totalHeight = -1;
while (!cursor.isTail() && !cursor.isInvalid()) {
cursor.invalidate();
cursor = cursor.getNext();
}
}
float getScale() {
return block.getSize() / font.getCharSet().getRenderedSize();
}
public boolean isPrintable() {
return current.getBitmapChar() != null;
}
float getTotalWidth() {
validateSize();
return totalWidth;
}
float getTotalHeight() {
validateSize();
return totalHeight;
}
void validateSize() {
if (totalWidth < 0) {
LetterQuad l = head;
while (!l.isTail()) {
totalWidth = Math.max(totalWidth, l.getX1());
l = l.getNext();
totalHeight = Math.max(totalHeight, -l.getY1());
}
}
}
/**
* @param start start index to set style. inclusive.
* @param end end index to set style. EXCLUSIVE.
* @param style
*/
void setStyle(int start, int end, int style) {
LetterQuad cursor = head.getNext();
while (!cursor.isTail()) {
if (cursor.getIndex() >= start && cursor.getIndex() < end) {
cursor.setStyle(style);
}
cursor = cursor.getNext();
}
}
/**
* Sets the base color for all new letter quads and resets
* the color of existing letter quads.
*/
void setColor( ColorRGBA color ) {
baseColor = color;
setColor( 0, block.getText().length(), color );
}
ColorRGBA getBaseColor() {
return baseColor;
}
/**
* @param start start index to set style. inclusive.
* @param end end index to set style. EXCLUSIVE.
* @param color
*/
void setColor(int start, int end, ColorRGBA color) {
LetterQuad cursor = head.getNext();
while (!cursor.isTail()) {
if (cursor.getIndex() >= start && cursor.getIndex() < end) {
cursor.setColor(color);
}
cursor = cursor.getNext();
}
}
}