aboutsummaryrefslogtreecommitdiff
path: root/engine/src/core/com/jme3/font/LetterQuad.java
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/core/com/jme3/font/LetterQuad.java')
-rw-r--r--engine/src/core/com/jme3/font/LetterQuad.java496
1 files changed, 496 insertions, 0 deletions
diff --git a/engine/src/core/com/jme3/font/LetterQuad.java b/engine/src/core/com/jme3/font/LetterQuad.java
new file mode 100644
index 0000000..09f0e24
--- /dev/null
+++ b/engine/src/core/com/jme3/font/LetterQuad.java
@@ -0,0 +1,496 @@
+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';
+ }
+
+}