Home | History | Annotate | Download | only in font
      1 package com.jme3.font;
      2 
      3 import com.jme3.font.BitmapFont.Align;
      4 import com.jme3.font.BitmapFont.VAlign;
      5 import com.jme3.font.ColorTags.Range;
      6 import com.jme3.math.ColorRGBA;
      7 import java.util.LinkedList;
      8 
      9 /**
     10  * Manage and align LetterQuads
     11  * @author YongHoon
     12  */
     13 class Letters {
     14     private final LetterQuad head;
     15     private final LetterQuad tail;
     16     private final BitmapFont font;
     17     private LetterQuad current;
     18     private StringBlock block;
     19     private float totalWidth;
     20     private float totalHeight;
     21     private ColorTags colorTags = new ColorTags();
     22     private ColorRGBA baseColor = null;
     23 
     24     Letters(BitmapFont font, StringBlock bound, boolean rightToLeft) {
     25         final String text = bound.getText();
     26         this.block = bound;
     27         this.font = font;
     28         head = new LetterQuad(font, rightToLeft);
     29         tail = new LetterQuad(font, rightToLeft);
     30         setText(text);
     31     }
     32 
     33     void setText(final String text) {
     34         colorTags.setText(text);
     35         String plainText = colorTags.getPlainText();
     36 
     37         head.setNext(tail);
     38         tail.setPrevious(head);
     39         current = head;
     40         if (text != null && plainText.length() > 0) {
     41             LetterQuad l = head;
     42             for (int i = 0; i < plainText.length(); i++) {
     43                 l = l.addNextCharacter(plainText.charAt(i));
     44                 if (baseColor != null) {
     45                     // Give the letter a default color if
     46                     // one has been provided.
     47                     l.setColor( baseColor );
     48                 }
     49             }
     50         }
     51 
     52         LinkedList<Range> ranges = colorTags.getTags();
     53         if (!ranges.isEmpty()) {
     54             for (int i = 0; i < ranges.size()-1; i++) {
     55                 Range start = ranges.get(i);
     56                 Range end = ranges.get(i+1);
     57                 setColor(start.start, end.start, start.color);
     58             }
     59             Range end = ranges.getLast();
     60             setColor(end.start, plainText.length(), end.color);
     61         }
     62 
     63         invalidate();
     64     }
     65 
     66     LetterQuad getHead() {
     67         return head;
     68     }
     69 
     70     LetterQuad getTail() {
     71         return tail;
     72     }
     73 
     74     void update() {
     75         LetterQuad l = head;
     76         int lineCount = 1;
     77         BitmapCharacter ellipsis = font.getCharSet().getCharacter(block.getEllipsisChar());
     78         float ellipsisWidth = ellipsis!=null? ellipsis.getWidth()*getScale(): 0;
     79 
     80         while (!l.isTail()) {
     81             if (l.isInvalid()) {
     82                 l.update(block);
     83 
     84                 if (l.isInvalid(block)) {
     85                     switch (block.getLineWrapMode()) {
     86                     case Character:
     87                         lineWrap(l);
     88                         lineCount++;
     89                         break;
     90                     case Word:
     91                         if (!l.isBlank()) {
     92                             // search last blank character before this word
     93                             LetterQuad blank = l;
     94                             while (!blank.isBlank()) {
     95                                 if (blank.isLineStart() || blank.isHead()) {
     96                                     lineWrap(l);
     97                                     lineCount++;
     98                                     blank = null;
     99                                     break;
    100                                 }
    101                                 blank = blank.getPrevious();
    102                             }
    103                             if (blank != null) {
    104                                 blank.setEndOfLine();
    105                                 lineCount++;
    106                                 while (blank != l) {
    107                                     blank = blank.getNext();
    108                                     blank.invalidate();
    109                                     blank.update(block);
    110                                 }
    111                             }
    112                         }
    113                         break;
    114                     case NoWrap:
    115                         // search last blank character before this word
    116                         LetterQuad cursor = l.getPrevious();
    117                         while (cursor.isInvalid(block, ellipsisWidth) && !cursor.isLineStart()) {
    118                             cursor = cursor.getPrevious();
    119                         }
    120                         cursor.setBitmapChar(ellipsis);
    121                         cursor.update(block);
    122                         cursor = cursor.getNext();
    123                         while (!cursor.isTail() && !cursor.isLineFeed()) {
    124                             cursor.setBitmapChar(null);
    125                             cursor.update(block);
    126                             cursor = cursor.getNext();
    127                         }
    128                         break;
    129                     }
    130                 }
    131             } else if (current.isInvalid(block)) {
    132                 invalidate(current);
    133             }
    134             if (l.isEndOfLine()) {
    135                 lineCount++;
    136             }
    137             l = l.getNext();
    138         }
    139 
    140         align();
    141         block.setLineCount(lineCount);
    142         rewind();
    143     }
    144 
    145     private void align() {
    146         final Align alignment = block.getAlignment();
    147         final VAlign valignment = block.getVerticalAlignment();
    148         if (block.getTextBox() == null || (alignment == Align.Left && valignment == VAlign.Top))
    149             return;
    150         LetterQuad cursor = tail.getPrevious();
    151         cursor.setEndOfLine();
    152         final float width = block.getTextBox().width;
    153         final float height = block.getTextBox().height;
    154         float lineWidth = 0;
    155         float gapX = 0;
    156         float gapY = 0;
    157         validateSize();
    158         if (totalHeight < height) { // align vertically only for no overflow
    159             switch (valignment) {
    160             case Top:
    161                 gapY = 0;
    162                 break;
    163             case Center:
    164                 gapY = (height-totalHeight)*0.5f;
    165                 break;
    166             case Bottom:
    167                 gapY = height-totalHeight;
    168                 break;
    169             }
    170         }
    171         while (!cursor.isHead()) {
    172             if (cursor.isEndOfLine()) {
    173                 lineWidth = cursor.getX1()-block.getTextBox().x;
    174                 if (alignment == Align.Center) {
    175                     gapX = (width-lineWidth)/2;
    176                 } else if (alignment == Align.Right) {
    177                     gapX = width-lineWidth;
    178                 } else {
    179                     gapX = 0;
    180                 }
    181             }
    182             cursor.setAlignment(gapX, gapY);
    183             cursor = cursor.getPrevious();
    184         }
    185     }
    186 
    187     private void lineWrap(LetterQuad l) {
    188         if (l.isHead() || l.isBlank())
    189             return;
    190         l.getPrevious().setEndOfLine();
    191         l.invalidate();
    192         l.update(block); // TODO: update from l
    193     }
    194 
    195     float getCharacterX0() {
    196         return current.getX0();
    197     }
    198 
    199     float getCharacterY0() {
    200         return current.getY0();
    201     }
    202 
    203     float getCharacterX1() {
    204         return current.getX1();
    205     }
    206 
    207     float getCharacterY1() {
    208         return current.getY1();
    209     }
    210 
    211     float getCharacterAlignX() {
    212         return current.getAlignX();
    213     }
    214 
    215     float getCharacterAlignY() {
    216         return current.getAlignY();
    217     }
    218 
    219     float getCharacterWidth() {
    220         return current.getWidth();
    221     }
    222 
    223     float getCharacterHeight() {
    224         return current.getHeight();
    225     }
    226 
    227     public boolean nextCharacter() {
    228         if (current.isTail())
    229             return false;
    230         current = current.getNext();
    231         return true;
    232     }
    233 
    234     public int getCharacterSetPage() {
    235         return current.getBitmapChar().getPage();
    236     }
    237 
    238     public LetterQuad getQuad() {
    239         return current;
    240     }
    241 
    242     public void rewind() {
    243         current = head;
    244     }
    245 
    246     public void invalidate() {
    247         invalidate(head);
    248     }
    249 
    250     public void invalidate(LetterQuad cursor) {
    251         totalWidth = -1;
    252         totalHeight = -1;
    253 
    254         while (!cursor.isTail() && !cursor.isInvalid()) {
    255             cursor.invalidate();
    256             cursor = cursor.getNext();
    257         }
    258     }
    259 
    260     float getScale() {
    261         return block.getSize() / font.getCharSet().getRenderedSize();
    262     }
    263 
    264     public boolean isPrintable() {
    265         return current.getBitmapChar() != null;
    266     }
    267 
    268     float getTotalWidth() {
    269         validateSize();
    270         return totalWidth;
    271     }
    272 
    273     float getTotalHeight() {
    274         validateSize();
    275         return totalHeight;
    276     }
    277 
    278     void validateSize() {
    279         if (totalWidth < 0) {
    280             LetterQuad l = head;
    281             while (!l.isTail()) {
    282                 totalWidth = Math.max(totalWidth, l.getX1());
    283                 l = l.getNext();
    284                 totalHeight = Math.max(totalHeight, -l.getY1());
    285             }
    286         }
    287     }
    288 
    289     /**
    290      * @param start start index to set style. inclusive.
    291      * @param end   end index to set style. EXCLUSIVE.
    292      * @param style
    293      */
    294     void setStyle(int start, int end, int style) {
    295         LetterQuad cursor = head.getNext();
    296         while (!cursor.isTail()) {
    297             if (cursor.getIndex() >= start && cursor.getIndex() < end) {
    298                 cursor.setStyle(style);
    299             }
    300             cursor = cursor.getNext();
    301         }
    302     }
    303 
    304     /**
    305      * Sets the base color for all new letter quads and resets
    306      * the color of existing letter quads.
    307      */
    308     void setColor( ColorRGBA color ) {
    309         baseColor = color;
    310         setColor( 0, block.getText().length(), color );
    311     }
    312 
    313     ColorRGBA getBaseColor() {
    314         return baseColor;
    315     }
    316 
    317     /**
    318      * @param start start index to set style. inclusive.
    319      * @param end   end index to set style. EXCLUSIVE.
    320      * @param color
    321      */
    322     void setColor(int start, int end, ColorRGBA color) {
    323         LetterQuad cursor = head.getNext();
    324         while (!cursor.isTail()) {
    325             if (cursor.getIndex() >= start && cursor.getIndex() < end) {
    326                 cursor.setColor(color);
    327             }
    328             cursor = cursor.getNext();
    329         }
    330     }
    331 }