BitmapText new features

Added some useful features.

  1. Multi-style text (rich text)
  • ex) plain and bold font can be handled in one BitmapFont
  • by loading two font and merging (texture is not automatically generated yet)
  1. Texture Coloring
  • current implementation also has one, but not easy to do it programatically.
  1. No Line Wrap Mode
  • Don’t cross the border



    I coded BitmapFont.updateText() from scratch because it is hard to understand and inefficient.





    [patch]

    Index: src/core/com/jme3/font/LetterQuad.java

    ===================================================================

    — src/core/com/jme3/font/LetterQuad.java (revision 0)

    +++ src/core/com/jme3/font/LetterQuad.java (revision 0)

    @@ -0,0 +1,447 @@

    +package com.jme3.font;

    +

    +import java.nio.ByteBuffer;

    +import java.nio.FloatBuffer;

    +import java.nio.ShortBuffer;

    +import java.util.Arrays;

    +

    +import com.jme3.font.BitmapCharacter;

    +import com.jme3.font.BitmapCharacterSet;

    +import com.jme3.font.BitmapFont;

    +import com.jme3.font.Rectangle;

    +import com.jme3.font.StringBlock;

    +import com.jme3.math.ColorRGBA;

    +

    +public 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 static final float TAB_WIDTH = 30;

    +
  • 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 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;

    +
  • /**
    • create head / tail
    • @param font
    • @param rightToLeft
  • */
  • protected LetterQuad(BitmapFont font, boolean rightToLeft) {
  •   this.font = font;<br />
    
  •   this.c = Character.MIN_VALUE;<br />
    
  •   this.rightToLeft = rightToLeft;<br />
    
  •   this.index = -1;<br />
    
  •   setBitmapChar(null);<br />
    
  • }

    +
  • /**
    • create letter and append to prev
  • *
    • @param c
    • @param prev previous character
  • */
  • protected LetterQuad(char c, LetterQuad prev) {
  •   this.font = prev.font;<br />
    
  •   this.rightToLeft = prev.rightToLeft;<br />
    
  •   this.c = c;<br />
    
  •   this.index = prev.index+1;<br />
    
  •   this.eol = isLineFeed();<br />
    
  •   setBitmapChar(c);<br />
    
  •   prev.insert(this);<br />
    
  • }

    +
  • LetterQuad addNextCharacter(char c) {
  •   LetterQuad n = new LetterQuad(c, this);<br />
    
  •   return n;<br />
    
  • }

    +
  • BitmapCharacter getBitmapChar() {
  •   return bitmapChar;<br />
    
  • }

    +
  • char getChar() {
  •   return c;<br />
    
  • }

    +
  • int getIndex() {
  •   return index;<br />
    
  • }

    +
  • private Rectangle getBound(StringBlock block) {
  •   if (block.getTextBox() != null) {<br />
    
  •   	return block.getTextBox();<br />
    
  •   }<br />
    
  •   return UNBOUNDED;<br />
    
  • }

    +
  • LetterQuad getPrevious() {
  •   return previous;<br />
    
  • }

    +
  • LetterQuad getNext() {
  •   return next;<br />
    
  • }

    +
  • public float getU0() {
  •   return u0;<br />
    
  • }

    +
  • float getU1() {
  •   return u1;<br />
    
  • }

    +
  • float getV0() {
  •   return v0;<br />
    
  • }

    +
  • float getV1() {
  •   return v1;<br />
    
  • }

    +
  • boolean isInvalid() {
  •    return x0 == Integer.MIN_VALUE;<br />
    
  • }

    +
  • boolean isInvalid(StringBlock block) {
  •   return isInvalid(block, 0);<br />
    
  • }

    +
  • boolean isInvalid(StringBlock block, float gap) {
  •   if (isHead() || isTail())<br />
    
  •   	return false;<br />
    
  •   if (x0 == Integer.MIN_VALUE || y0 == Integer.MIN_VALUE) {<br />
    
  •   	return true;<br />
    
  •   }<br />
    
  •   Rectangle bound = block.getTextBox();<br />
    
  •   if (bound == null) {<br />
    
  •   	return false;<br />
    
  •   }<br />
    
  •   return x0 &gt; 0 &amp;&amp; bound.x+bound.width-gap &lt; getX1();<br />
    
  • }

    +
  • float getX0() {
  •   return x0;<br />
    
  • }

    +
  • float getX1() {
  •   return x0+width;<br />
    
  • }

    +
  • float getY0() {
  •   return y0;<br />
    
  • }

    +
  • float getY1() {
  •   return y0-height;<br />
    
  • }

    +
  • float getWidth() {
  •   return width;<br />
    
  • }

    +
  • float getHeight() {
  •   return height;<br />
    
  • }

    +
  • void insert(LetterQuad ins) {
  •   LetterQuad n = next;<br />
    
  •   next = ins;<br />
    
  •   ins.next = n;<br />
    
  •   ins.previous = this;<br />
    
  •   n.previous = ins;<br />
    
  • }

    +
  • void invalidate() {
  •   eol = isLineFeed();<br />
    
  •   setBitmapChar(font.getCharSet().getCharacter(c, style));<br />
    
  • }

    +
  • boolean isTail() {
  •   return next == null;<br />
    
  • }

    +
  • boolean isHead() {
  •   return previous == null;<br />
    
  • }

    +
  • /**
    • @return next letter
  • */
  • LetterQuad remove() {
  •   this.previous.next = next;<br />
    
  •   this.next.previous = previous;<br />
    
  •   return next;<br />
    
  • }

    +
  • void setPrevious(LetterQuad before) {
  •   this.previous = before;<br />
    
  • }

    +
  • void setStyle(int style) {
  •   this.style = style;<br />
    
  •   invalidate();<br />
    
  • }

    +
  • void setColor(ColorRGBA color) {
  •   this.colorInt = color.asIntRGBA();<br />
    
  •   invalidate();<br />
    
  • }

    +
  • void setBitmapChar(char c) {
  •   BitmapCharacterSet charSet = font.getCharSet();<br />
    
  •   BitmapCharacter bm = charSet.getCharacter(c, style);<br />
    
  •   setBitmapChar(bm);<br />
    
  • }

    +
  • void setBitmapChar(BitmapCharacter bitmapChar) {
  •   x0 = Integer.MIN_VALUE;<br />
    
  •    y0 = Integer.MIN_VALUE;<br />
    
  •    width = Integer.MIN_VALUE;<br />
    
  •    height = Integer.MIN_VALUE;<br />
    

+

  •   BitmapCharacterSet charSet = font.getCharSet();<br />
    
  •   this.bitmapChar = bitmapChar;<br />
    
  •   if (bitmapChar != null) {<br />
    
  •       u0 = (float) bitmapChar.getX() / charSet.getWidth();<br />
    
  •       v0 = (float) bitmapChar.getY() / charSet.getHeight();<br />
    
  •       u1 = u0 + (float) bitmapChar.getWidth() / charSet.getWidth();<br />
    
  •       v1 = v0 + (float) bitmapChar.getHeight() / charSet.getHeight();<br />
    
  •   } else {<br />
    
  •       u0 = 0;<br />
    
  •       v0 = 0;<br />
    
  •       u1 = 0;<br />
    
  •       v1 = 0;<br />
    
  •   }<br />
    
  • }

    +
  • void setNext(LetterQuad next) {
  •   this.next = next;<br />
    
  • }

    +
  • void update(StringBlock block) {
  •   Rectangle bound = getBound(block);<br />
    
  •   float sizeScale = block.getSize() / font.getCharSet().getRenderedSize();<br />
    
  •   computeLineY(block, sizeScale);<br />
    

+

  •   if (isTab()) {<br />
    
  •       x0 = previous.getX0();<br />
    
  •       y0 = lineY;<br />
    
  •       width = TAB_WIDTH;<br />
    
  •       height = 0;<br />
    
  •   } else if (bitmapChar == null) {<br />
    
  •   	x0 = getBound(block).x;<br />
    
  •   	y0 = lineY;<br />
    
  •   	width = 0;<br />
    
  •   	height = 0;<br />
    
  •   } else {<br />
    
  •       float xOffset = bitmapChar.getXOffset() * sizeScale;<br />
    
  •       float yOffset = bitmapChar.getYOffset() * sizeScale;<br />
    

+// float xAdvance = bitmapChar.getXAdvance() * sizeScale;

  •       width = bitmapChar.getWidth() * sizeScale;<br />
    
  •       height = bitmapChar.getHeight() * sizeScale;<br />
    
  •       float incrScale = rightToLeft ? -1f : 1f;<br />
    
  •       float kernAmount = 0f;<br />
    

+

  •       if (x0 == Integer.MIN_VALUE) {<br />
    
  •           if (previous.isHead() || previous.eol) {<br />
    
  •               x0 = bound.x;<br />
    
  •           } else {<br />
    
  •               x0 = previous.getX1() + xOffset * incrScale;<br />
    
  •           }<br />
    
  •           y0 = lineY + LINE_DIR*yOffset;<br />
    
  •       }<br />
    

+

  •       // Adjust for kerning<br />
    
  •       BitmapCharacter lastChar = previous.getBitmapChar();<br />
    
  •       if (lastChar != null &amp;&amp; block.isKerning()) {<br />
    
  •           int amount = lastChar.getKerning(c);<br />
    
  •           if (amount != -1) {<br />
    
  •               kernAmount = amount * sizeScale;<br />
    
  •               x0 += kernAmount * incrScale;<br />
    
  •           }<br />
    
  •       }<br />
    
  •   }<br />
    
  • }

    +
  • private boolean isLineStartButNot1st() {
  •    return !previous.isHead() &amp;&amp; isLineStart();<br />
    
  • }
  • /**
  • * add temporary linewrap indicator<br />
    
  • */<br />
    
  • void setEndOfLine() {
  •   this.eol = true;<br />
    
  • }

    +
  • boolean isEndOfLine() {
  •    return eol;<br />
    
  • }

    +
  • boolean isLineWrap() {
  •    return !isHead() &amp;&amp; !isTail() &amp;&amp; bitmapChar == null &amp;&amp; c == Character.MIN_VALUE;<br />
    
  • }

    +
  • void computeLineY(StringBlock block, float sizeScale) {
  •   if (isHead()) {<br />
    
  •   		lineY = getBound(block).y;<br />
    
  •   } else if (previous.eol) {<br />
    
  •   	lineY = previous.lineY+LINE_DIR*font.getCharSet().getLineHeight() * sizeScale;<br />
    
  •   } else {<br />
    
  •   	lineY = previous.lineY;<br />
    
  •   }<br />
    
  • }

    +

    +
  • boolean isLineStart() {
  •   return x0 == 0 || (previous != null &amp;&amp; previous.eol);<br />
    
  • }

    +
  • boolean isBlank() {
  •   return c == ' ' || isTab();<br />
    
  • }

    +
  • public void storeToArrays(float[] pos, float[] tc, short[] idx, byte[] colors, int quadIdx){
  •   float x = x0+alignX;<br />
    
  •   float y = y0-alignY;<br />
    
  •   float xpw = x0+width;<br />
    
  •   float ymh = y0-height;<br />
    

+

  •   pos[0] = x;   pos[1]  = y;   pos[2]  = 0;<br />
    
  •   pos[3] = x;   pos[4]  = ymh; pos[5]  = 0;<br />
    
  •   pos[6] = xpw; pos[7]  = ymh; pos[8]  = 0;<br />
    
  •   pos[9] = xpw; pos[10] = y;   pos[11] = 0;<br />
    

+

  •   float v0 = 1f - this.v0;<br />
    
  •   float v1 = 1f - this.v1;<br />
    

+

  •   tc[0] = u0; tc[1] = v0;<br />
    
  •   tc[2] = u0; tc[3] = v1;<br />
    
  •   tc[4] = u1; tc[5] = v1;<br />
    
  •   tc[6] = u1; tc[7] = v0;<br />
    

+

  •   colors[0] = (byte) (colorInt &amp; 0xff);<br />
    
  •   colors[1] = (byte) ((colorInt &gt;&gt; 8) &amp; 0xff);<br />
    
  •   colors[2] = (byte) ((colorInt &gt;&gt; 16) &amp; 0xff);<br />
    
  •   colors[3] = (byte) ((colorInt &gt;&gt; 24) &amp; 0xff);<br />
    
  •   System.arraycopy(colors, 0, colors, 4,  4);<br />
    
  •   System.arraycopy(colors, 0, colors, 8,  4);<br />
    
  •   System.arraycopy(colors, 0, colors, 12, 4);<br />
    

+

  •   short i0 = (short) (quadIdx * 4);<br />
    
  •   short i1 = (short) (i0 + 1);<br />
    
  •   short i2 = (short) (i0 + 2);<br />
    
  •   short i3 = (short) (i0 + 3);<br />
    

+

  •   idx[0] = i0; idx[1] = i1; idx[2] = i2;<br />
    
  •   idx[3] = i0; idx[4] = i2; idx[5] = i3;<br />
    
  • }

    +
  • public void appendPositions(FloatBuffer fb){
  •    // NOTE: subtracting the height here<br />
    
  •    // because OGL's Ortho origin is at lower-left<br />
    
  •   final float x1 = x0+width;<br />
    
  •   final float y1 = y0-height;<br />
    
  •    fb.put(x0).put(y0).put(0f);<br />
    
  •    fb.put(x0).put(y1).put(0f);<br />
    
  •    fb.put(x1).put(y1).put(0f);<br />
    
  •    fb.put(x1).put(y0).put(0f);<br />
    
  • }

    +
  • public void appendPositions(ShortBuffer sb){
  •    final float x1 = getX1();<br />
    
  •    final float y1 = getY1();<br />
    
  •    short x = (short) x0;<br />
    
  •    short y = (short) y0;<br />
    
  •    short xpw = (short) (x1);<br />
    
  •    short ymh = (short) (y1);<br />
    

+

  •    sb.put(x).put(y).put((short)0);<br />
    
  •    sb.put(x).put(ymh).put((short)0);<br />
    
  •    sb.put(xpw).put(ymh).put((short)0);<br />
    
  •    sb.put(xpw).put(y).put((short)0);<br />
    
  • }

    +
  • public void appendTexCoords(FloatBuffer fb){
  •    // flip coords to be compatible with OGL<br />
    
  •    float v0 = 1 - this.v0;<br />
    
  •    float v1 = 1 - this.v1;<br />
    

+

  •    // upper left<br />
    
  •    fb.put(u0).put(v0);<br />
    
  •    // lower left<br />
    
  •    fb.put(u0).put(v1);<br />
    
  •    // lower right<br />
    
  •    fb.put(u1).put(v1);<br />
    
  •    // upper right<br />
    
  •    fb.put(u1).put(v0);<br />
    
  • }

    +
  • public void appendColors(ByteBuffer bb){
  •    bb.putInt(colorInt);<br />
    
  •    bb.putInt(colorInt);<br />
    
  •    bb.putInt(colorInt);<br />
    
  •    bb.putInt(colorInt);<br />
    
  • }

    +
  • public void appendIndices(ShortBuffer sb, int quadIndex){
  •    // each quad has 4 indices<br />
    
  •    short v0 = (short) (quadIndex * 4);<br />
    
  •    short v1 = (short) (v0 + 1);<br />
    
  •    short v2 = (short) (v0 + 2);<br />
    
  •    short v3 = (short) (v0 + 3);<br />
    

+

  •    sb.put(v0).put(v1).put(v2);<br />
    
  •    sb.put(v0).put(v2).put(v3);<br />
    

+// sb.put(new short[]{ v0, v1, v2,

+// v0, v2, v3 });

  • }

    +

    +
  • @Override
  • public String toString() {
  •   return String.valueOf(c);<br />
    
  • }

    +
  • void setAlignment(float alignX, float alignY) {
  •    this.alignX = alignX;<br />
    
  •    this.alignY = alignY;<br />
    
  • }

    +
  • float getAlignX() {
  •    return alignX;<br />
    
  • }

    +
  • float getAlignY() {
  •    return alignY;<br />
    
  • }

    +
  • boolean isLineFeed() {
  •    return c == 'n';<br />
    
  • }

    +
  • boolean isTab() {
  •    return c == 't';<br />
    
  • }

    +

    +}

    Index: src/core/com/jme3/font/LineWrapType.java

    ===================================================================

    — src/core/com/jme3/font/LineWrapType.java (revision 0)

    +++ src/core/com/jme3/font/LineWrapType.java (revision 0)

    @@ -0,0 +1,7 @@

    +package com.jme3.font;

    +

    +public enum LineWrapType {
  • CHARACTER,
  • WORD,
  • ELLIPSIS

    +}

    Index: src/core/com/jme3/font/BitmapCharacterSet.java

    ===================================================================

    — src/core/com/jme3/font/BitmapCharacterSet.java (revision 6754)

    +++ src/core/com/jme3/font/BitmapCharacterSet.java (working copy)

    @@ -32,14 +32,17 @@



    package com.jme3.font;



    +import java.io.IOException;

    +import java.util.LinkedList;

    +import java.util.List;

    +

    +import com.jme3.export.InputCapsule;

    import com.jme3.export.JmeExporter;

    import com.jme3.export.JmeImporter;

    -import com.jme3.export.InputCapsule;

    import com.jme3.export.OutputCapsule;

    import com.jme3.export.Savable;

    import com.jme3.util.IntMap;

    import com.jme3.util.IntMap.Entry;

    -import java.io.IOException;



    public class BitmapCharacterSet implements Savable {



    @@ -48,8 +51,9 @@

    private int renderedSize;

    private int width;

    private int height;
  • private IntMap<BitmapCharacter> characters;
  • private IntMap<IntMap<BitmapCharacter>> characters;


  • @Override

    public void write(JmeExporter ex) throws IOException {

    OutputCapsule oc = ex.getCapsule(this);

    oc.write(lineHeight, "lineHeight", 0);

    @@ -58,20 +62,34 @@

    oc.write(width, "width", 0);

    oc.write(height, "height", 0);


  •    int size = characters.size();<br />
    
  •    int[] styles = new int[characters.size()];<br />
    
  •    int index = 0;<br />
    
  •    for (Entry&lt;IntMap&lt;BitmapCharacter&gt;&gt; entry : characters) {<br />
    
  •        int style = entry.getKey();<br />
    
  •        styles[index] = style;<br />
    
  •        index++;<br />
    
  •        IntMap&lt;BitmapCharacter&gt; charset = entry.getValue();<br />
    
  •        writeCharset(oc, style, charset);<br />
    
  •    }<br />
    
  •    oc.write(styles, &quot;styles&quot;, null);<br />
    
  • }

    +
  • protected void writeCharset(OutputCapsule oc, int style, IntMap<BitmapCharacter> charset) throws IOException {
  •    int size = charset.size();<br />
    

short[] indexes = new short[size];

BitmapCharacter[] chars = new BitmapCharacter[size];

int i = 0;

  •    for (Entry&lt;BitmapCharacter&gt; chr : characters){<br />
    
  •    for (Entry&lt;BitmapCharacter&gt; chr : charset){<br />
    

indexes = (short) chr.getKey();

chars = chr.getValue();

i++;

}


  •    oc.write(indexes, &quot;indexes&quot;, null);<br />
    
  •    oc.write(chars,   &quot;chars&quot;,   null);<br />
    
  •    oc.write(indexes, &quot;indexes&quot;+style, null);<br />
    
  •    oc.write(chars,   &quot;chars&quot;+style,   null);<br />
    

}


  • @Override

    public void read(JmeImporter im) throws IOException {

    InputCapsule ic = im.getCapsule(this);

    lineHeight = ic.readInt("lineHeight", 0);

    @@ -79,27 +97,51 @@

    renderedSize = ic.readInt("renderedSize", 0);

    width = ic.readInt("width", 0);

    height = ic.readInt("height", 0);
  •    int[] styles = ic.readIntArray(&quot;styles&quot;, null);<br />
    

+

  •    for (int style : styles) {<br />
    
  •        characters.put(style, readCharset(ic, style));<br />
    
  •    }<br />
    
  • }


  •    short[] indexes = ic.readShortArray(&quot;indexes&quot;, null);<br />
    
  •    Savable[] chars = ic.readSavableArray(&quot;chars&quot;, null);<br />
    
  • private IntMap<BitmapCharacter> readCharset(InputCapsule ic, int style) throws IOException {
  •    IntMap&lt;BitmapCharacter&gt; charset = new IntMap&lt;BitmapCharacter&gt;();<br />
    
  •    short[] indexes = ic.readShortArray(&quot;indexes&quot;+style, null);<br />
    
  •    Savable[] chars = ic.readSavableArray(&quot;chars&quot;+style, null);<br />
    

for (int i = 0; i < indexes.length; i++){
int index = indexes & 0xFFFF;
BitmapCharacter chr = (BitmapCharacter) chars;
- characters.put(index, chr);
+ charset.put(index, chr);
}
+ return charset;
}

public BitmapCharacterSet() {
- characters = new IntMap<BitmapCharacter>();
+ characters = new IntMap<IntMap<BitmapCharacter>>();
}

public BitmapCharacter getCharacter(int index){
- return characters.get(index);
+ return getCharacter(index, 0);
+ }
+
+ public BitmapCharacter getCharacter(int index, int style){
+ IntMap<BitmapCharacter> map = getCharacterSet(style);
+ if (map != null) {
+ return map.get(index);
+ }
+ return null;
+ }
+
+ private IntMap<BitmapCharacter> getCharacterSet(int style) {
+ if (characters.size() == 0) {
+ characters.put(style, new IntMap<BitmapCharacter>());
+ }
+ return characters.get(style);
}

public void addCharacter(int index, BitmapCharacter ch){
- characters.put(index, ch);
+ getCharacterSet(0).put(index, ch);
}

public int getLineHeight() {
@@ -141,4 +183,41 @@
public void setHeight(int height) {
this.height = height;
}
+
+ public void merge(BitmapCharacterSet that) {
+ if (this.renderedSize != that.renderedSize) {
+ throw new RuntimeException("Only support same font size");
+ }
+ for (Entry<IntMap<BitmapCharacter>> entry : that.characters) {
+ int style = entry.getKey();
+ IntMap<BitmapCharacter> charset = entry.getValue();
+ int shiftx = 0;
+ int shifty = 0;
+ if (this.height > this.width) {
+ shifty = this.height;
+ } else {
+ shiftx = this.width;
+ }
+ this.lineHeight = Math.max(this.lineHeight, that.lineHeight);
+ this.width += shiftx;
+ this.height += shifty;
+ this.characters.put(style, charset);
+
+ for (Entry<BitmapCharacter> charEntry : charset) {
+ BitmapCharacter ch = charEntry.getValue();
+ ch.setX(ch.getX()+shiftx);
+ ch.setY(ch.getY()+shifty);
+ }
+ }
+ }
+
+ public void setStyle(int style) {
+ if (characters.size() > 1) {
+ throw new RuntimeException("Applicable only for single style font");
+ }
+ Entry<IntMap<BitmapCharacter>> entry = characters.iterator().next();
+ IntMap<BitmapCharacter> charset = entry.getValue();
+ characters.remove(entry.getKey());
+ characters.put(style, charset);
+ }
}
No newline at end of file
Index: src/core/com/jme3/font/Letters.java
===================================================================
--- src/core/com/jme3/font/Letters.java (revision 0)
+++ src/core/com/jme3/font/Letters.java (revision 0)
@@ -0,0 +1,278 @@
+package com.jme3.font;
+
+import com.jme3.font.BitmapFont.Align;
+import com.jme3.math.ColorRGBA;
+
+class Letters {
+ private static final char ellipsisChar = 0x2026;
+ private final BitmapCharacter ellipsis;
+ private final float ellipsisWidth;
+ 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 int lineCount;
+
+ 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);
+ ellipsis = font.getCharSet().getCharacter(ellipsisChar);
+ ellipsisWidth = ellipsis!=null? ellipsis.getWidth()*getScale(): 0;
+ }
+
+ void setText(final CharSequence text) {
+ head.setNext(tail);
+ tail.setPrevious(head);
+ current = head;
+ if (text != null && text.length() > 0) {
+ LetterQuad l = head;
+ for (int i = 0; i < text.length(); i++) {
+ l = l.addNextCharacter(text.charAt(i));
+ }
+ }
+ }
+
+ LetterQuad getHead() {
+ return head;
+ }
+
+ LetterQuad getTail() {
+ return tail;
+ }
+
+ void update() {
+ LetterQuad l = head;
+ lineCount = 1;
+ while (!l.isTail()) {
+ if (l.isInvalid()) {
+ l.update(block);
+
+ if (l.isInvalid(block)) {
+ switch (block.getLineWrapType()) {
+ case CHARACTER:
+ lineWrap(l);
+ 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);
+ blank = null;
+ break;
+ }
+ blank = blank.getPrevious();
+ }
+ if (blank != null) {
+ blank.setEndOfLine();
+ while (blank != l) {
+ blank = blank.getNext();
+ blank.invalidate();
+ blank.update(block);
+ }
+ }
+ }
+ break;
+ case ELLIPSIS:
+ // 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 = cursor.getNext();
+ while (!cursor.isTail() && !cursor.isLineFeed()) {
+ cursor.setBitmapChar(null);
+ cursor = cursor.getNext();
+ }
+ break;
+ }
+ }
+ } else if (current.isInvalid(block)) {
+ invalidate(current);
+ }
+ if (l.isEndOfLine()) {
+ lineCount++;
+ }
+ l = l.getNext();
+ }
+
+ align(block.getAlignment());
+ rewind();
+ }
+
+ private void align(Align alignment) {
+ if (block.getTextBox() == null || alignment == Align.Left)
+ return;
+ LetterQuad cursor = tail.getPrevious();
+ cursor.setEndOfLine();
+ final float width = block.getTextBox().width;
+ float lineWidth = 0;
+ float gapX = 0;
+ float gapY = 0;
+ while (!cursor.isHead()) {
+ if (cursor.isEndOfLine()) {
+ if (cursor.isLineFeed()) {
+ lineWidth = cursor.getPrevious().getX1()-block.getTextBox().x;
+ } else {
+ 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);
+ }
+
+ float getX0() {
+ return current.getX0();
+ }
+
+ float getY0() {
+ return current.getY0();
+ }
+
+ float getX1() {
+ return current.getX1();
+ }
+
+ float getY1() {
+ return current.getY1();
+ }
+
+ float getAlignX() {
+ return current.getAlignX();
+ }
+
+ float getAlignY() {
+ return current.getAlignY();
+ }
+
+ float getWidth() {
+ return current.getWidth();
+ }
+
+ float getHeight() {
+ return current.getHeight();
+ }
+
+ int getLineCount() {
+ return lineCount;
+ }
+
+ public boolean nextCharacter() {
+ if (current.isTail())
+ return false;
+ current = current.getNext();
+ return true;
+ }
+
+ public int getPage() {
+ 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;
+ lineCount = 0;
+
+ 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();
+ }
+ }
+
+ /**
+ * @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();
+ }
+ }
+
+}
No newline at end of file
Index: src/core/com/jme3/font/StringBlock.java
===================================================================
--- src/core/com/jme3/font/StringBlock.java (revision 6754)
+++ src/core/com/jme3/font/StringBlock.java (working copy)
@@ -45,11 +45,12 @@

private StringBuilder text;
private Rectangle textBox;
- private BitmapFont.Align alignment;
+ private BitmapFont.Align alignment = Align.Left;
private float size;
private ColorRGBA color = new ColorRGBA(ColorRGBA.White);
private boolean kerning;
private int lineCount;
+ private LineWrapType wrapType = LineWrapType.WORD;

/**
*
@@ -156,5 +157,17 @@
void setLineCount(int lineCount) {
this.lineCount = lineCount;
}
+
+ public LineWrapType getLineWrapType() {
+ return wrapType;
+ }
+
+ /**
+ * available only when bounding is set. <code>setBox()</code> method call is needed in advance.
+ * @param wrap true when word need not be split at the end of the line.
+ */
+ public void setLineWrapType(LineWrapType wrap) {
+ this.wrapType = wrap;
+ }

}
No newline at end of file
Index: src/core/com/jme3/font/BitmapText.java
===================================================================
--- src/core/com/jme3/font/BitmapText.java (revision 6754)
+++ src/core/com/jme3/font/BitmapText.java (working copy)
@@ -31,6 +31,11 @@
*/
package com.jme3.font;

+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.renderer.RenderManager;
@@ -43,10 +48,9 @@
private StringBlock block;
private float lineWidth = 0f;
private boolean needRefresh = true;
- private boolean rightToLeft = false;
- private boolean wordWrap = true;
private final BitmapTextPage[] textPages;
private QuadList quadList = new QuadList();
+ private Letters letters;

public BitmapText(BitmapFont font) {
this(font, false, false);
@@ -68,6 +72,7 @@
this.font = font;
this.block = new StringBlock();
block.setSize(font.getPreferredSize());
+ letters = new Letters(font, block, rightToLeft);
}

@Override
@@ -92,6 +97,7 @@
public void setSize(float size) {
block.setSize(size);
needRefresh = true;
+ letters.invalidate();
}

/**
@@ -104,6 +110,7 @@
}

block.setText(text);
+ letters.setText(text);
needRefresh = true;
}

@@ -122,15 +129,12 @@
}

/**
- * changes text color
+ * changes text color. all substring colors are deleted.
* @param color new color of text
*/
public void setColor(ColorRGBA color) {
- if (block.getColor().equals(color)) {
- return;
- }
-
- block.setColor(color);
+ letters.setColor(0, block.getText().length(), color);
+ letters.invalidate(); // TODO: Don't have to align.
needRefresh = true;
}

@@ -140,6 +144,7 @@
*/
public void setBox(Rectangle rect) {
block.setTextBox(rect);
+ letters.invalidate();
needRefresh = true;
}

@@ -157,7 +162,7 @@
if (needRefresh) {
assemble();
}
- float height = getLineHeight()*block.getLineCount();
+ float height = getLineHeight()*letters.getLineCount();
Rectangle textBox = block.getTextBox();
if (textBox != null) {
return Math.max(height, textBox.height);
@@ -175,22 +180,77 @@
return lineWidth;
}

- public boolean isWordWrap() {
- return wordWrap;
+ public LineWrapType getLineWrap() {
+ return block.getLineWrapType();
}

public void setAlignment(BitmapFont.Align align) {
block.setAlignment(align);
+ letters.invalidate();
+ needRefresh = true;
}

/**
- * available only when bounding is set. <code>setBox()</code> method call is needed in advance.
- * @param wordWrap true when word need not be split at the end of the line.
+ * Set the font style of substring. If font doesn't contain style, default style is used
+ * @param start start index to set style. inclusive.
+ * @param end end index to set style. EXCLUSIVE.
+ * @param style
+ */
+ public void setStyle(int start, int end, int style) {
+ letters.setStyle(start, end, style);
+ }
+
+ /**
+ * Set the font style of substring. If font doesn't contain style, default style is used
+ * @param regexp regular expression
+ * @param style
+ */
+ public void setStyle(String regexp, int style) {
+ Pattern p = Pattern.compile(regexp);
+ Matcher m = p.matcher(block.getText());
+ while (m.find()) {
+ setStyle(m.start(), m.end(), style);
+ }
+ }
+
+ /**
+ * Set the color of substring.
+ * @param start start index to set style. inclusive.
+ * @param end end index to set style. EXCLUSIVE.
+ * @param color
*/
- public void setWordWrap(boolean wordWrap) {
- this.wordWrap = wordWrap;
+ public void setColor(int start, int end, ColorRGBA color) {
+ letters.setColor(start, end, color);
+ letters.invalidate();
needRefresh = true;
}
+
+ /**
+ * Set the color of substring.
+ * @param regexp regular expression
+ * @param color
+ */
+ public void setColor(String regexp, ColorRGBA color) {
+ Pattern p = Pattern.compile(regexp);
+ Matcher m = p.matcher(block.getText());
+ while (m.find()) {
+ letters.setColor(m.start(), m.end(), color);
+ }
+ letters.invalidate();
+ needRefresh = true;
+ }
+
+ /**
+ * available only when bounding is set. <code>setBox()</code> method call is needed in advance.
+ * @param wrap true when word need not be split at the end of the line.
+ */
+ public void setLineWrap(LineWrapType wrap) {
+ if (block.getLineWrapType() != wrap) {
+ block.setLineWrapType(wrap);
+ letters.invalidate();
+ needRefresh = true;
+ }
+ }

public BitmapFont.Align getAlignment() {
return block.getAlignment();
@@ -206,15 +266,10 @@

private void assemble() {
// first generate quadlist
- quadList.clear();
- if (block.getTextBox() == null) {
- lineWidth = font.updateText(block, quadList, rightToLeft);
- } else {
- lineWidth = font.updateTextRect(block, quadList, wordWrap);
- }
+ letters.update();

for (int i = 0; i < textPages.length; i++) {
- textPages.assemble(quadList);
+ textPages.assemble(letters);
}
needRefresh = false;
}
@@ -226,4 +281,22 @@
mat.render(page, rm);
}
}
+
+ public QuadList getQuadList() {
+ return quadList;
+ }
+
+ @Deprecated
+ public void setWordWrap(boolean wrap) {
+ if (wrap) {
+ setLineWrap(LineWrapType.WORD);
+ } else {
+ setLineWrap(LineWrapType.CHARACTER);
+ }
+ }
+
+ @Deprecated
+ public boolean isWordWrap() {
+ return isWordWrap();
+ }
}
Index: src/core/com/jme3/font/FontQuad.java
===================================================================
--- src/core/com/jme3/font/FontQuad.java (revision 6754)
+++ src/core/com/jme3/font/FontQuad.java (working copy)
@@ -227,6 +227,10 @@
public void setWordNumber(int number) {
wordNumber = number;
}
+
+ public float getCharacterWidth() {
+ return quadPosWidth;
+ }

public float getSizeScale() {
return sizeScale;
Index: src/core/com/jme3/font/BitmapFont.java
===================================================================
--- src/core/com/jme3/font/BitmapFont.java (revision 6754)
+++ src/core/com/jme3/font/BitmapFont.java (working copy)
@@ -39,6 +39,10 @@
import com.jme3.export.Savable;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
+import com.jme3.shader.VarType;
+import com.jme3.texture.Texture;
+
+import java.awt.Font;
import java.io.IOException;

/**
@@ -47,6 +51,9 @@
*/
public class BitmapFont implements Savable {

+ private static final char ELLIPSIS = '.';
+ private static final int ELLIPSIS_LENGTH = 3;
+
public enum Align {
Left, Center, Right
}
@@ -328,8 +335,10 @@
return Math.max(lineWidth, maxLineWidth);
}

- float updateTextRect(StringBlock b, QuadList target, boolean wordWrap) {
+ float updateTextRect(StringBlock b, QuadList target) {

+ final LineWrapType wrapType = b.getLineWrapType();
+ final int ellipsisWidth = charSet.getCharacter(ELLIPSIS).getWidth()*ELLIPSIS_LENGTH;
String text = b.getText();
float x = b.getTextBox().x;
float y = b.getTextBox().y;
@@ -345,6 +354,7 @@
boolean firstCharOfLine = true;
boolean useKerning = b.isKerning();
Align alignment = b.getAlignment();
+ int ellipsisCount = 0;

target.clear();

@@ -362,6 +372,7 @@

wordNumber = 1;
lineNumber++;
+ ellipsisCount = 0;
}else if (c == null){
System.out.println("Character '" + text.charAt(i) + "' is not in alphabet, skipping it.");
}else{
@@ -373,10 +384,10 @@

// Newline
if (lineWidth + xAdvance >= maxWidth){
- x = b.getTextBox().x;
- y -= charSet.getLineHeight() * sizeScale;
// float offset = 0f;
- if ((lineWidth + xAdvance >= maxWidth) && (wordNumber != 1) && wordWrap){
+ if ((lineWidth + xAdvance >= maxWidth) && (wordNumber != 1) && wrapType==LineWrapType.WORD){
+ x = b.getTextBox().x;
+ y -= charSet.getLineHeight() * sizeScale;
// Next character extends past text box width
// We have to move the last word down one line
char newLineLastChar = 0;
@@ -389,15 +400,15 @@
FontQuad q = target.getQuad(j);
BitmapCharacter localChar = q.getBitmapChar();

- float localxOffset = localChar.getXOffset() * sizeScale;
- float localyOffset = localChar.getYOffset() * sizeScale;
- float localxAdvance = localChar.getXAdvance() * sizeScale;
-
// Move current word to the left side of the text box
if ((q.getLineNumber() == lineNumber) && (q.getWordNumber() == wordNumber)){
if (alignment == Align.Left && q.getCharacter() == ' '){
continue;
}
+ float localxOffset = localChar.getXOffset() * sizeScale;
+ float localyOffset = localChar.getYOffset() * sizeScale;
+ float localxAdvance = localChar.getXAdvance() * sizeScale;
+
q.setLineNumber(q.getLineNumber() + 1);
q.setWordNumber(1);
float quadPosX = x + localxOffset;
@@ -435,18 +446,85 @@
}
}
}
+ wordNumber = 1;
+ lineNumber++;
+ } else if ((lineWidth + xAdvance >= maxWidth-ellipsisWidth) && wrapType==LineWrapType.ELLIPSIS){
+ // Next character extends past text box width
+ // We have to move the last word down one line
+ char newLineLastChar = 0;
+ maxLineWidth = Math.max(lineWidth, maxLineWidth);
+ lastLineWidth = lineWidth;

- }else{
+ for (int j = 0; j < target.getActualSize(); j++){
+ FontQuad q = target.getQuad(j);
+ BitmapCharacter localChar = q.getBitmapChar();
+
+ // Move current word to the left side of the text box
+ if ((q.getLineNumber() == lineNumber) && (q.getX()+q.getCharacterWidth() > maxWidth-ellipsisWidth)){
+// if (alignment == Align.Left && q.getCharacter() == ' '){
+// continue;
+// }
+ if (ellipsisCount < ELLIPSIS_LENGTH) {
+ localChar = charSet.getCharacter(ELLIPSIS);
+ setQuadCharacter(q, ELLIPSIS, sizeScale);
+ float localxOffset = localChar.getXOffset() * sizeScale;
+ float localxAdvance = localChar.getXAdvance() * sizeScale;
+
+ float quadPosX = q.getX() + localxOffset;
+ q.setPosition(quadPosX, y);
+
+ x = quadPosX + localxAdvance;
+ lastLineWidth = x;
+ lineWidth += localxAdvance;
+ int amount = findKerningAmount(newLineLastChar, q.getCharacter());
+ if (amount != -1 && useKerning){
+ x += amount * sizeScale;
+ lineWidth += amount * sizeScale;
+ }
+ ellipsisCount++;
+ xOffset = localChar.getXOffset() * sizeScale;
+ yOffset = localChar.getYOffset() * sizeScale;
+ xAdvance = localChar.getXAdvance() * sizeScale;
+ width = localChar.getWidth() * sizeScale;
+ height = localChar.getHeight() * sizeScale;
+ } else {
+ q.setSize(0, 0);
+ }
+ }
+
+ newLineLastChar = q.getCharacter();
+ }
+
+ // Justify the previous (now complete) line
+ if (alignment == Align.Center){
+ for (int k = 0; k < target.getQuantity(); k++){
+ FontQuad q = target.getQuad(k);
+
+ if (q.getLineNumber() == lineNumber){
+ q.setX(q.getX() + b.getTextBox().width / 2f - lastLineWidth / 2f);
+ }
+ }
+ }
+ if (alignment == Align.Right){
+ for (int k = 0; k < target.getQuantity(); k++){
+ FontQuad q = target.getQuad(k);
+ if (q.getLineNumber() == lineNumber){
+ q.setX(q.getX() + b.getTextBox().width - lastLineWidth);
+ }
+ }
+ }
+
+ } else{
+ x = b.getTextBox().x;
+ y -= charSet.getLineHeight() * sizeScale;
// New line without any "carry-down" word
firstCharOfLine = true;
maxLineWidth = Math.max(lineWidth, maxLineWidth);
lastLineWidth = lineWidth;
lineWidth = 0f;
+ wordNumber = 1;
+ lineNumber++;
}
-
- wordNumber = 1;
- lineNumber++;
-
} // End new line check

// Dont print these
@@ -478,16 +556,9 @@
float quadPosX = x + (xOffset);
float quadPosY = y - yOffset;
q.setPosition(quadPosX, quadPosY);
- q.setSize(width, height);
-
x += xAdvance;
lineWidth += xAdvance;

- float u0 = (float) c.getX() / charSet.getWidth();
- float v0 = (float) c.getY() / charSet.getHeight();
- float w = (float) c.getWidth() / charSet.getWidth();
- float h = (float) c.getHeight() / charSet.getHeight();
- q.setUV(u0, v0, w, h);
q.setColor(b.getColor());

q.setLineNumber(lineNumber);
@@ -498,9 +569,10 @@
q.setWordNumber(wordNumber);
wordWidth += xAdvance;
q.setWordWidth(wordWidth);
- q.setBitmapChar(c);
+// if (ellipsisCount == 0) {
+ setQuadCharacter(q, text.charAt(i), sizeScale);
+// }
q.setSizeScale(sizeScale);
- q.setCharacter(text.charAt(i));

lastChar = text.charAt(i);

@@ -529,4 +601,27 @@
return Math.max(maxLineWidth,lineWidth);
}

+ private void setQuadCharacter(FontQuad q, char ch, float sizeScale) {
+ BitmapCharacter c = charSet.getCharacter(ch);
+ q.setBitmapChar(c);
+ q.setCharacter(ch);
+ float u0 = (float) c.getX() / charSet.getWidth();
+ float v0 = (float) c.getY() / charSet.getHeight();
+ float w = (float) c.getWidth() / charSet.getWidth();
+ float h = (float) c.getHeight() / charSet.getHeight();
+ q.setUV(u0, v0, w, h);
+ q.setSize(c.getWidth()*sizeScale, c.getHeight()*sizeScale);
+ }
+
+ public void merge(BitmapFont font2, Texture[] fontTexture) {
+ charSet.merge(font2.charSet);
+ for (int i = 0; i < fontTexture.length; i++) {
+ getPage(i).setTextureParam("ColorMap", VarType.Texture2D, fontTexture);
+ }
+ }
+
+ public void setStyle(int style) {
+ charSet.setStyle(style);
+ }
+
}
No newline at end of file
Index: src/core/com/jme3/font/BitmapTextPage.java
===================================================================
--- src/core/com/jme3/font/BitmapTextPage.java (revision 6754)
+++ src/core/com/jme3/font/BitmapTextPage.java (working copy)
@@ -34,6 +34,7 @@
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
+import java.util.LinkedList;

import com.jme3.material.Material;
import com.jme3.scene.Geometry;
@@ -51,7 +52,7 @@
private final byte[] color;
private final int page;
private final Texture2D texture;
- private final QuadList quadList = new QuadList();
+ private final LinkedList<LetterQuad> pageQuads = new LinkedList<LetterQuad>();

BitmapTextPage(BitmapFont font, boolean arrayBased, int page) {
super("BitmapFont", new Mesh());
@@ -114,11 +115,19 @@
return clone;
}

- void assemble(QuadList quads) {
- quads.getPage(page, quadList);
+ void assemble(Letters quads) {
+ pageQuads.clear();
+ quads.rewind();
+ while (quads.nextCharacter()) {
+ if (quads.isPrintable()) {
+ if (quads.getPage() == page) {
+ pageQuads.add(quads.getQuad());
+ }
+ }
+ }
Mesh m = getMesh();
- m.setVertexCount(quadList.getQuantity() * 4);
- m.setTriangleCount(quadList.getQuantity() * 2);
+ m.setVertexCount(pageQuads.size() * 4);
+ m.setTriangleCount(pageQuads.size() * 2);

VertexBuffer pb = m.getBuffer(Type.Position);
VertexBuffer tb = m.getBuffer(Type.TexCoord);
@@ -153,8 +162,8 @@

// go for each quad and append it to the buffers
if (pos != null) {
- for (int i = 0; i < quadList.getActualSize(); i++) {
- FontQuad fq = quadList.getQuad(i);
+ for (int i = 0; i < pageQuads.size(); i++) {
+ LetterQuad fq = pageQuads.get(i);
fq.storeToArrays(pos, tc, idx, color, i);
fpb.put(pos);
ftb.put(tc);
@@ -162,8 +171,8 @@
bcb.put(color);
}
} else {
- for (int i = 0; i < quadList.getActualSize(); i++) {
- FontQuad fq = quadList.getQuad(i);
+ for (int i = 0; i < pageQuads.size(); i++) {
+ LetterQuad fq = pageQuads.get(i);
fq.appendPositions(fpb);
fq.appendTexCoords(ftb);
fq.appendIndices(sib, i);

[/patch]
2 Likes

Good job!

Its about time we got rid of that directly-from-C# ported implementation :slight_smile:



I assume the API for BitmapText won’t change?

Also, did you test this with nifty gui? Since the text-color changing is used there and you changed it, it might not work

Yes, there is no public method change but just some deprecated methods.

It works well with TestNiftyGUI.

Old implementation describes the text color in the text itself and I didn’t implement it.

Wait for me to add this feature and commit.

committed in svn. rev.6768

I tested it for various test case. I hope it doesn’t break any existing code.

If you have needs for another features. let me know. :slight_smile:



Added features below.

  1. Multi-style text (rich text)
  • Font can be merged. It works by adding BitmapTextPage internally.
  • So plain,bold, italic font can be handled in one BitmapFont.


  1. Texture Coloring
  • (1) programatically. call setColor(regex, color) or setColor(start, end, color)
  • (2) by tag. #rgb# or #rrggbb# (as before)


  1. No Line Wrap Mode
  • If NoWrap mode is set, text isn’t wrapped but ignored.

    and the last character is set to ellipsis.(by default, ‘…’ the code is 0x2026)

    it can be changed by setEllipsisChar()


  1. Tab support
  • You can set the tab location.
  • You can specify the tab width.


  1. Vertical Alignment
1 Like

Hey, the nightly build of jME3 is broken since the commit:


com.sun.xml.internal.fastinfoset.stax.events does not exist
[javac] import com.sun.xml.internal.fastinfoset.stax.events.CharactersEvent;

Any idea to circumvent this?

Hm with eclipse you can build it. Just updated jme 5 minutes ago, no problems so far.

Sorry. It was my mistake. fixed rev. 6770

I am not suppose to import it.

In the setStyle(start,end,style), what are the allowed values of the Style parameter ?

eg if I wanted to set style as Italic, what value would I use ?