Home | History | Annotate | Download | only in font
      1 package com.jme3.font;
      2 
      3 import com.jme3.math.ColorRGBA;
      4 import java.nio.ByteBuffer;
      5 import java.nio.FloatBuffer;
      6 import java.nio.ShortBuffer;
      7 
      8 /**
      9  * LetterQuad contains the position, color, uv texture information for a character in text.
     10  * @author YongHoon
     11  */
     12 class LetterQuad {
     13     private static final Rectangle UNBOUNDED = new Rectangle(0, 0, Float.MAX_VALUE, Float.MAX_VALUE);
     14     private static final float LINE_DIR = -1;
     15 
     16     private final BitmapFont font;
     17     private final char c;
     18     private final int index;
     19     private int style;
     20 
     21     private BitmapCharacter bitmapChar = null;
     22     private float x0 = Integer.MIN_VALUE;
     23     private float y0 = Integer.MIN_VALUE;
     24     private float width = Integer.MIN_VALUE;
     25     private float height = Integer.MIN_VALUE;
     26     private float xAdvance = 0;
     27     private float u0;
     28     private float v0;
     29     private float u1;
     30     private float v1;
     31     private float lineY;
     32     private boolean eol;
     33 
     34     private LetterQuad previous;
     35     private LetterQuad next;
     36     private int colorInt = 0xFFFFFFFF;
     37 
     38     private boolean rightToLeft;
     39     private float alignX;
     40     private float alignY;
     41     private float sizeScale = 1;
     42 
     43     /**
     44      * create head / tail
     45      * @param font
     46      * @param rightToLeft
     47      */
     48     protected LetterQuad(BitmapFont font, boolean rightToLeft) {
     49         this.font = font;
     50         this.c = Character.MIN_VALUE;
     51         this.rightToLeft = rightToLeft;
     52         this.index = -1;
     53         setBitmapChar(null);
     54     }
     55 
     56     /**
     57      * create letter and append to previous LetterQuad
     58      *
     59      * @param c
     60      * @param prev previous character
     61      */
     62     protected LetterQuad(char c, LetterQuad prev) {
     63         this.font = prev.font;
     64         this.rightToLeft = prev.rightToLeft;
     65         this.c = c;
     66         this.index = prev.index+1;
     67         this.eol = isLineFeed();
     68         setBitmapChar(c);
     69         prev.insert(this);
     70     }
     71 
     72     LetterQuad addNextCharacter(char c) {
     73         LetterQuad n = new LetterQuad(c, this);
     74         return n;
     75     }
     76 
     77     BitmapCharacter getBitmapChar() {
     78         return bitmapChar;
     79     }
     80 
     81     char getChar() {
     82         return c;
     83     }
     84 
     85     int getIndex() {
     86         return index;
     87     }
     88 
     89     private Rectangle getBound(StringBlock block) {
     90         if (block.getTextBox() != null) {
     91             return block.getTextBox();
     92         }
     93         return UNBOUNDED;
     94     }
     95 
     96     LetterQuad getPrevious() {
     97         return previous;
     98     }
     99 
    100     LetterQuad getNext() {
    101         return next;
    102     }
    103 
    104     public float getU0() {
    105         return u0;
    106     }
    107 
    108     float getU1() {
    109         return u1;
    110     }
    111 
    112     float getV0() {
    113         return v0;
    114     }
    115 
    116     float getV1() {
    117         return v1;
    118     }
    119 
    120     boolean isInvalid() {
    121         return x0 == Integer.MIN_VALUE;
    122     }
    123 
    124     boolean isInvalid(StringBlock block) {
    125         return isInvalid(block, 0);
    126     }
    127 
    128     boolean isInvalid(StringBlock block, float gap) {
    129         if (isHead() || isTail())
    130             return false;
    131         if (x0 == Integer.MIN_VALUE || y0 == Integer.MIN_VALUE) {
    132             return true;
    133         }
    134         Rectangle bound = block.getTextBox();
    135         if (bound == null) {
    136             return false;
    137         }
    138         return x0 > 0 && bound.x+bound.width-gap < getX1();
    139     }
    140 
    141     float getX0() {
    142         return x0;
    143     }
    144 
    145     float getX1() {
    146         return x0+width;
    147     }
    148 
    149     float getNextX() {
    150         return x0+xAdvance;
    151     }
    152 
    153     float getNextLine() {
    154         return lineY+LINE_DIR*font.getCharSet().getLineHeight() * sizeScale;
    155     }
    156 
    157     float getY0() {
    158         return y0;
    159     }
    160 
    161     float getY1() {
    162         return y0-height;
    163     }
    164 
    165     float getWidth() {
    166         return width;
    167     }
    168 
    169     float getHeight() {
    170         return height;
    171     }
    172 
    173     void insert(LetterQuad ins) {
    174         LetterQuad n = next;
    175         next = ins;
    176         ins.next = n;
    177         ins.previous = this;
    178         n.previous = ins;
    179     }
    180 
    181     void invalidate() {
    182         eol = isLineFeed();
    183         setBitmapChar(font.getCharSet().getCharacter(c, style));
    184     }
    185 
    186     boolean isTail() {
    187         return next == null;
    188     }
    189 
    190     boolean isHead() {
    191         return previous == null;
    192     }
    193 
    194     /**
    195      * @return next letter
    196      */
    197     LetterQuad remove() {
    198         this.previous.next = next;
    199         this.next.previous = previous;
    200         return next;
    201     }
    202 
    203     void setPrevious(LetterQuad before) {
    204         this.previous = before;
    205     }
    206 
    207     void setStyle(int style) {
    208         this.style = style;
    209         invalidate();
    210     }
    211 
    212     void setColor(ColorRGBA color) {
    213         this.colorInt = color.asIntRGBA();
    214         invalidate();
    215     }
    216 
    217     void setBitmapChar(char c) {
    218         BitmapCharacterSet charSet = font.getCharSet();
    219         BitmapCharacter bm = charSet.getCharacter(c, style);
    220         setBitmapChar(bm);
    221     }
    222 
    223     void setBitmapChar(BitmapCharacter bitmapChar) {
    224         x0 = Integer.MIN_VALUE;
    225         y0 = Integer.MIN_VALUE;
    226         width = Integer.MIN_VALUE;
    227         height = Integer.MIN_VALUE;
    228         alignX = 0;
    229         alignY = 0;
    230 
    231         BitmapCharacterSet charSet = font.getCharSet();
    232         this.bitmapChar = bitmapChar;
    233         if (bitmapChar != null) {
    234             u0 = (float) bitmapChar.getX() / charSet.getWidth();
    235             v0 = (float) bitmapChar.getY() / charSet.getHeight();
    236             u1 = u0 + (float) bitmapChar.getWidth() / charSet.getWidth();
    237             v1 = v0 + (float) bitmapChar.getHeight() / charSet.getHeight();
    238         } else {
    239             u0 = 0;
    240             v0 = 0;
    241             u1 = 0;
    242             v1 = 0;
    243         }
    244     }
    245 
    246     void setNext(LetterQuad next) {
    247         this.next = next;
    248     }
    249 
    250     void update(StringBlock block) {
    251         final float[] tabs = block.getTabPosition();
    252         final float tabWidth = block.getTabWidth();
    253         final Rectangle bound = getBound(block);
    254         sizeScale = block.getSize() / font.getCharSet().getRenderedSize();
    255         lineY = computeLineY(block);
    256 
    257         if (isHead()) {
    258             x0 = getBound(block).x;
    259             y0 = lineY;
    260             width = 0;
    261             height = 0;
    262             xAdvance = 0;
    263         } else if (isTab()) {
    264             x0 = previous.getNextX();
    265             width = tabWidth;
    266             y0 = lineY;
    267             height = 0;
    268             if (tabs != null && x0 < tabs[tabs.length-1]) {
    269                 for (int i = 0; i < tabs.length-1; i++) {
    270                     if (x0 > tabs[i] && x0 < tabs[i+1]) {
    271                         width = tabs[i+1] - x0;
    272                     }
    273                 }
    274             }
    275             xAdvance = width;
    276         } else if (bitmapChar == null) {
    277             x0 = getPrevious().getX1();
    278             y0 = lineY;
    279             width = 0;
    280             height = 0;
    281             xAdvance = 0;
    282         } else {
    283             float xOffset = bitmapChar.getXOffset() * sizeScale;
    284             float yOffset = bitmapChar.getYOffset() * sizeScale;
    285             xAdvance = bitmapChar.getXAdvance() * sizeScale;
    286             width = bitmapChar.getWidth() * sizeScale;
    287             height = bitmapChar.getHeight() * sizeScale;
    288             float incrScale = rightToLeft ? -1f : 1f;
    289             float kernAmount = 0f;
    290 
    291             if (previous.isHead() || previous.eol) {
    292                 x0 = bound.x;
    293 
    294                 // The first letter quad will be drawn right at the first
    295                 // position... but it does not offset by the characters offset
    296                 // amount.  This means that we've potentially accumulated extra
    297                 // pixels and the next letter won't get drawn far enough unless
    298                 // we add this offset back into xAdvance.. by subtracting it.
    299                 // This is the same thing that's done below because we've
    300                 // technically baked the offset in just like below.  It doesn't
    301                 // look like it at first glance so I'm keeping it separate with
    302                 // this comment.
    303                 xAdvance -= xOffset * incrScale;
    304 
    305             } else {
    306                 x0 = previous.getNextX() + xOffset * incrScale;
    307 
    308                 // Since x0 will have offset baked into it then we
    309                 // need to counteract that in xAdvance.  This is better
    310                 // than removing it in getNextX() because we also need
    311                 // to take kerning into account below... which will also
    312                 // get baked in.
    313                 // Without this, getNextX() will return values too far to
    314                 // the left, for example.
    315                 xAdvance -= xOffset * incrScale;
    316             }
    317             y0 = lineY + LINE_DIR*yOffset;
    318 
    319             // Adjust for kerning
    320             BitmapCharacter lastChar = previous.getBitmapChar();
    321             if (lastChar != null && block.isKerning()) {
    322                 kernAmount = lastChar.getKerning(c) * sizeScale;
    323                 x0 += kernAmount * incrScale;
    324 
    325                 // Need to unbake the kerning from xAdvance since it
    326                 // is baked into x0... see above.
    327                 //xAdvance -= kernAmount * incrScale;
    328                 // No, kerning is an inter-character spacing and _does_ affect
    329                 // all subsequent cursor positions.
    330             }
    331         }
    332         if (isEndOfLine()) {
    333             xAdvance = bound.x-x0;
    334         }
    335     }
    336 
    337     /**
    338      * add temporary linewrap indicator
    339      */
    340     void setEndOfLine() {
    341         this.eol = true;
    342     }
    343 
    344     boolean isEndOfLine() {
    345         return eol;
    346     }
    347 
    348     boolean isLineWrap() {
    349         return !isHead() && !isTail() && bitmapChar == null && c == Character.MIN_VALUE;
    350     }
    351 
    352     private float computeLineY(StringBlock block) {
    353         if (isHead()) {
    354             return getBound(block).y;
    355         } else if (previous.eol) {
    356             return previous.getNextLine();
    357         } else {
    358             return previous.lineY;
    359         }
    360     }
    361 
    362 
    363     boolean isLineStart() {
    364         return x0 == 0 || (previous != null && previous.eol);
    365     }
    366 
    367     boolean isBlank() {
    368         return c == ' ' || isTab();
    369     }
    370 
    371     public void storeToArrays(float[] pos, float[] tc, short[] idx, byte[] colors, int quadIdx){
    372         float x = x0+alignX;
    373         float y = y0-alignY;
    374         float xpw = x+width;
    375         float ymh = y-height;
    376 
    377         pos[0] = x;   pos[1]  = y;   pos[2]  = 0;
    378         pos[3] = x;   pos[4]  = ymh; pos[5]  = 0;
    379         pos[6] = xpw; pos[7]  = ymh; pos[8]  = 0;
    380         pos[9] = xpw; pos[10] = y;   pos[11] = 0;
    381 
    382         float v0 = 1f - this.v0;
    383         float v1 = 1f - this.v1;
    384 
    385         tc[0] = u0; tc[1] = v0;
    386         tc[2] = u0; tc[3] = v1;
    387         tc[4] = u1; tc[5] = v1;
    388         tc[6] = u1; tc[7] = v0;
    389 
    390         colors[3] = (byte) (colorInt & 0xff);
    391         colors[2] = (byte) ((colorInt >> 8) & 0xff);
    392         colors[1] = (byte) ((colorInt >> 16) & 0xff);
    393         colors[0] = (byte) ((colorInt >> 24) & 0xff);
    394         System.arraycopy(colors, 0, colors, 4,  4);
    395         System.arraycopy(colors, 0, colors, 8,  4);
    396         System.arraycopy(colors, 0, colors, 12, 4);
    397 
    398         short i0 = (short) (quadIdx * 4);
    399         short i1 = (short) (i0 + 1);
    400         short i2 = (short) (i0 + 2);
    401         short i3 = (short) (i0 + 3);
    402 
    403         idx[0] = i0; idx[1] = i1; idx[2] = i2;
    404         idx[3] = i0; idx[4] = i2; idx[5] = i3;
    405     }
    406 
    407     public void appendPositions(FloatBuffer fb){
    408         float sx = x0+alignX;
    409         float sy = y0-alignY;
    410         float ex = sx+width;
    411         float ey = sy-height;
    412         // NOTE: subtracting the height here
    413         // because OGL's Ortho origin is at lower-left
    414         fb.put(sx).put(sy).put(0f);
    415         fb.put(sx).put(ey).put(0f);
    416         fb.put(ex).put(ey).put(0f);
    417         fb.put(ex).put(sy).put(0f);
    418     }
    419 
    420     public void appendPositions(ShortBuffer sb){
    421         final float x1 = getX1();
    422         final float y1 = getY1();
    423         short x = (short) x0;
    424         short y = (short) y0;
    425         short xpw = (short) (x1);
    426         short ymh = (short) (y1);
    427 
    428         sb.put(x).put(y).put((short)0);
    429         sb.put(x).put(ymh).put((short)0);
    430         sb.put(xpw).put(ymh).put((short)0);
    431         sb.put(xpw).put(y).put((short)0);
    432     }
    433 
    434     public void appendTexCoords(FloatBuffer fb){
    435         // flip coords to be compatible with OGL
    436         float v0 = 1 - this.v0;
    437         float v1 = 1 - this.v1;
    438 
    439         // upper left
    440         fb.put(u0).put(v0);
    441         // lower left
    442         fb.put(u0).put(v1);
    443         // lower right
    444         fb.put(u1).put(v1);
    445         // upper right
    446         fb.put(u1).put(v0);
    447     }
    448 
    449     public void appendColors(ByteBuffer bb){
    450         bb.putInt(colorInt);
    451         bb.putInt(colorInt);
    452         bb.putInt(colorInt);
    453         bb.putInt(colorInt);
    454     }
    455 
    456     public void appendIndices(ShortBuffer sb, int quadIndex){
    457         // each quad has 4 indices
    458         short v0 = (short) (quadIndex * 4);
    459         short v1 = (short) (v0 + 1);
    460         short v2 = (short) (v0 + 2);
    461         short v3 = (short) (v0 + 3);
    462 
    463         sb.put(v0).put(v1).put(v2);
    464         sb.put(v0).put(v2).put(v3);
    465 //        sb.put(new short[]{ v0, v1, v2,
    466 //                            v0, v2, v3 });
    467     }
    468 
    469 
    470     @Override
    471     public String toString() {
    472         return String.valueOf(c);
    473     }
    474 
    475     void setAlignment(float alignX, float alignY) {
    476         this.alignX = alignX;
    477         this.alignY = alignY;
    478     }
    479 
    480     float getAlignX() {
    481         return alignX;
    482     }
    483 
    484     float getAlignY() {
    485         return alignY;
    486     }
    487 
    488     boolean isLineFeed() {
    489         return c == '\n';
    490     }
    491 
    492     boolean isTab() {
    493         return c == '\t';
    494     }
    495 
    496 }
    497