blob: 09f0e2431fa2934ca1b65c7c619fcc8371fb8e02 [file] [log] [blame]
package com.jme3.font;
import com.jme3.math.ColorRGBA;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
/**
* LetterQuad contains the position, color, uv texture information for a character in text.
* @author YongHoon
*/
class LetterQuad {
private static final Rectangle UNBOUNDED = new Rectangle(0, 0, Float.MAX_VALUE, Float.MAX_VALUE);
private static final float LINE_DIR = -1;
private final BitmapFont font;
private final char c;
private final int index;
private int style;
private BitmapCharacter bitmapChar = null;
private float x0 = Integer.MIN_VALUE;
private float y0 = Integer.MIN_VALUE;
private float width = Integer.MIN_VALUE;
private float height = Integer.MIN_VALUE;
private float xAdvance = 0;
private float u0;
private float v0;
private float u1;
private float v1;
private float lineY;
private boolean eol;
private LetterQuad previous;
private LetterQuad next;
private int colorInt = 0xFFFFFFFF;
private boolean rightToLeft;
private float alignX;
private float alignY;
private float sizeScale = 1;
/**
* create head / tail
* @param font
* @param rightToLeft
*/
protected LetterQuad(BitmapFont font, boolean rightToLeft) {
this.font = font;
this.c = Character.MIN_VALUE;
this.rightToLeft = rightToLeft;
this.index = -1;
setBitmapChar(null);
}
/**
* create letter and append to previous LetterQuad
*
* @param c
* @param prev previous character
*/
protected LetterQuad(char c, LetterQuad prev) {
this.font = prev.font;
this.rightToLeft = prev.rightToLeft;
this.c = c;
this.index = prev.index+1;
this.eol = isLineFeed();
setBitmapChar(c);
prev.insert(this);
}
LetterQuad addNextCharacter(char c) {
LetterQuad n = new LetterQuad(c, this);
return n;
}
BitmapCharacter getBitmapChar() {
return bitmapChar;
}
char getChar() {
return c;
}
int getIndex() {
return index;
}
private Rectangle getBound(StringBlock block) {
if (block.getTextBox() != null) {
return block.getTextBox();
}
return UNBOUNDED;
}
LetterQuad getPrevious() {
return previous;
}
LetterQuad getNext() {
return next;
}
public float getU0() {
return u0;
}
float getU1() {
return u1;
}
float getV0() {
return v0;
}
float getV1() {
return v1;
}
boolean isInvalid() {
return x0 == Integer.MIN_VALUE;
}
boolean isInvalid(StringBlock block) {
return isInvalid(block, 0);
}
boolean isInvalid(StringBlock block, float gap) {
if (isHead() || isTail())
return false;
if (x0 == Integer.MIN_VALUE || y0 == Integer.MIN_VALUE) {
return true;
}
Rectangle bound = block.getTextBox();
if (bound == null) {
return false;
}
return x0 > 0 && bound.x+bound.width-gap < getX1();
}
float getX0() {
return x0;
}
float getX1() {
return x0+width;
}
float getNextX() {
return x0+xAdvance;
}
float getNextLine() {
return lineY+LINE_DIR*font.getCharSet().getLineHeight() * sizeScale;
}
float getY0() {
return y0;
}
float getY1() {
return y0-height;
}
float getWidth() {
return width;
}
float getHeight() {
return height;
}
void insert(LetterQuad ins) {
LetterQuad n = next;
next = ins;
ins.next = n;
ins.previous = this;
n.previous = ins;
}
void invalidate() {
eol = isLineFeed();
setBitmapChar(font.getCharSet().getCharacter(c, style));
}
boolean isTail() {
return next == null;
}
boolean isHead() {
return previous == null;
}
/**
* @return next letter
*/
LetterQuad remove() {
this.previous.next = next;
this.next.previous = previous;
return next;
}
void setPrevious(LetterQuad before) {
this.previous = before;
}
void setStyle(int style) {
this.style = style;
invalidate();
}
void setColor(ColorRGBA color) {
this.colorInt = color.asIntRGBA();
invalidate();
}
void setBitmapChar(char c) {
BitmapCharacterSet charSet = font.getCharSet();
BitmapCharacter bm = charSet.getCharacter(c, style);
setBitmapChar(bm);
}
void setBitmapChar(BitmapCharacter bitmapChar) {
x0 = Integer.MIN_VALUE;
y0 = Integer.MIN_VALUE;
width = Integer.MIN_VALUE;
height = Integer.MIN_VALUE;
alignX = 0;
alignY = 0;
BitmapCharacterSet charSet = font.getCharSet();
this.bitmapChar = bitmapChar;
if (bitmapChar != null) {
u0 = (float) bitmapChar.getX() / charSet.getWidth();
v0 = (float) bitmapChar.getY() / charSet.getHeight();
u1 = u0 + (float) bitmapChar.getWidth() / charSet.getWidth();
v1 = v0 + (float) bitmapChar.getHeight() / charSet.getHeight();
} else {
u0 = 0;
v0 = 0;
u1 = 0;
v1 = 0;
}
}
void setNext(LetterQuad next) {
this.next = next;
}
void update(StringBlock block) {
final float[] tabs = block.getTabPosition();
final float tabWidth = block.getTabWidth();
final Rectangle bound = getBound(block);
sizeScale = block.getSize() / font.getCharSet().getRenderedSize();
lineY = computeLineY(block);
if (isHead()) {
x0 = getBound(block).x;
y0 = lineY;
width = 0;
height = 0;
xAdvance = 0;
} else if (isTab()) {
x0 = previous.getNextX();
width = tabWidth;
y0 = lineY;
height = 0;
if (tabs != null && x0 < tabs[tabs.length-1]) {
for (int i = 0; i < tabs.length-1; i++) {
if (x0 > tabs[i] && x0 < tabs[i+1]) {
width = tabs[i+1] - x0;
}
}
}
xAdvance = width;
} else if (bitmapChar == null) {
x0 = getPrevious().getX1();
y0 = lineY;
width = 0;
height = 0;
xAdvance = 0;
} else {
float xOffset = bitmapChar.getXOffset() * sizeScale;
float yOffset = bitmapChar.getYOffset() * sizeScale;
xAdvance = bitmapChar.getXAdvance() * sizeScale;
width = bitmapChar.getWidth() * sizeScale;
height = bitmapChar.getHeight() * sizeScale;
float incrScale = rightToLeft ? -1f : 1f;
float kernAmount = 0f;
if (previous.isHead() || previous.eol) {
x0 = bound.x;
// The first letter quad will be drawn right at the first
// position... but it does not offset by the characters offset
// amount. This means that we've potentially accumulated extra
// pixels and the next letter won't get drawn far enough unless
// we add this offset back into xAdvance.. by subtracting it.
// This is the same thing that's done below because we've
// technically baked the offset in just like below. It doesn't
// look like it at first glance so I'm keeping it separate with
// this comment.
xAdvance -= xOffset * incrScale;
} else {
x0 = previous.getNextX() + xOffset * incrScale;
// Since x0 will have offset baked into it then we
// need to counteract that in xAdvance. This is better
// than removing it in getNextX() because we also need
// to take kerning into account below... which will also
// get baked in.
// Without this, getNextX() will return values too far to
// the left, for example.
xAdvance -= xOffset * incrScale;
}
y0 = lineY + LINE_DIR*yOffset;
// Adjust for kerning
BitmapCharacter lastChar = previous.getBitmapChar();
if (lastChar != null && block.isKerning()) {
kernAmount = lastChar.getKerning(c) * sizeScale;
x0 += kernAmount * incrScale;
// Need to unbake the kerning from xAdvance since it
// is baked into x0... see above.
//xAdvance -= kernAmount * incrScale;
// No, kerning is an inter-character spacing and _does_ affect
// all subsequent cursor positions.
}
}
if (isEndOfLine()) {
xAdvance = bound.x-x0;
}
}
/**
* add temporary linewrap indicator
*/
void setEndOfLine() {
this.eol = true;
}
boolean isEndOfLine() {
return eol;
}
boolean isLineWrap() {
return !isHead() && !isTail() && bitmapChar == null && c == Character.MIN_VALUE;
}
private float computeLineY(StringBlock block) {
if (isHead()) {
return getBound(block).y;
} else if (previous.eol) {
return previous.getNextLine();
} else {
return previous.lineY;
}
}
boolean isLineStart() {
return x0 == 0 || (previous != null && previous.eol);
}
boolean isBlank() {
return c == ' ' || isTab();
}
public void storeToArrays(float[] pos, float[] tc, short[] idx, byte[] colors, int quadIdx){
float x = x0+alignX;
float y = y0-alignY;
float xpw = x+width;
float ymh = y-height;
pos[0] = x; pos[1] = y; pos[2] = 0;
pos[3] = x; pos[4] = ymh; pos[5] = 0;
pos[6] = xpw; pos[7] = ymh; pos[8] = 0;
pos[9] = xpw; pos[10] = y; pos[11] = 0;
float v0 = 1f - this.v0;
float v1 = 1f - this.v1;
tc[0] = u0; tc[1] = v0;
tc[2] = u0; tc[3] = v1;
tc[4] = u1; tc[5] = v1;
tc[6] = u1; tc[7] = v0;
colors[3] = (byte) (colorInt & 0xff);
colors[2] = (byte) ((colorInt >> 8) & 0xff);
colors[1] = (byte) ((colorInt >> 16) & 0xff);
colors[0] = (byte) ((colorInt >> 24) & 0xff);
System.arraycopy(colors, 0, colors, 4, 4);
System.arraycopy(colors, 0, colors, 8, 4);
System.arraycopy(colors, 0, colors, 12, 4);
short i0 = (short) (quadIdx * 4);
short i1 = (short) (i0 + 1);
short i2 = (short) (i0 + 2);
short i3 = (short) (i0 + 3);
idx[0] = i0; idx[1] = i1; idx[2] = i2;
idx[3] = i0; idx[4] = i2; idx[5] = i3;
}
public void appendPositions(FloatBuffer fb){
float sx = x0+alignX;
float sy = y0-alignY;
float ex = sx+width;
float ey = sy-height;
// NOTE: subtracting the height here
// because OGL's Ortho origin is at lower-left
fb.put(sx).put(sy).put(0f);
fb.put(sx).put(ey).put(0f);
fb.put(ex).put(ey).put(0f);
fb.put(ex).put(sy).put(0f);
}
public void appendPositions(ShortBuffer sb){
final float x1 = getX1();
final float y1 = getY1();
short x = (short) x0;
short y = (short) y0;
short xpw = (short) (x1);
short ymh = (short) (y1);
sb.put(x).put(y).put((short)0);
sb.put(x).put(ymh).put((short)0);
sb.put(xpw).put(ymh).put((short)0);
sb.put(xpw).put(y).put((short)0);
}
public void appendTexCoords(FloatBuffer fb){
// flip coords to be compatible with OGL
float v0 = 1 - this.v0;
float v1 = 1 - this.v1;
// upper left
fb.put(u0).put(v0);
// lower left
fb.put(u0).put(v1);
// lower right
fb.put(u1).put(v1);
// upper right
fb.put(u1).put(v0);
}
public void appendColors(ByteBuffer bb){
bb.putInt(colorInt);
bb.putInt(colorInt);
bb.putInt(colorInt);
bb.putInt(colorInt);
}
public void appendIndices(ShortBuffer sb, int quadIndex){
// each quad has 4 indices
short v0 = (short) (quadIndex * 4);
short v1 = (short) (v0 + 1);
short v2 = (short) (v0 + 2);
short v3 = (short) (v0 + 3);
sb.put(v0).put(v1).put(v2);
sb.put(v0).put(v2).put(v3);
// sb.put(new short[]{ v0, v1, v2,
// v0, v2, v3 });
}
@Override
public String toString() {
return String.valueOf(c);
}
void setAlignment(float alignX, float alignY) {
this.alignX = alignX;
this.alignY = alignY;
}
float getAlignX() {
return alignX;
}
float getAlignY() {
return alignY;
}
boolean isLineFeed() {
return c == '\n';
}
boolean isTab() {
return c == '\t';
}
}