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 
Letters(BitmapFont font, StringBlock bound, boolean rightToLeft)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 
setText(final String text)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 
getHead()66     LetterQuad getHead() {
67         return head;
68     }
69 
getTail()70     LetterQuad getTail() {
71         return tail;
72     }
73 
update()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 
align()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 
lineWrap(LetterQuad l)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 
getCharacterX0()195     float getCharacterX0() {
196         return current.getX0();
197     }
198 
getCharacterY0()199     float getCharacterY0() {
200         return current.getY0();
201     }
202 
getCharacterX1()203     float getCharacterX1() {
204         return current.getX1();
205     }
206 
getCharacterY1()207     float getCharacterY1() {
208         return current.getY1();
209     }
210 
getCharacterAlignX()211     float getCharacterAlignX() {
212         return current.getAlignX();
213     }
214 
getCharacterAlignY()215     float getCharacterAlignY() {
216         return current.getAlignY();
217     }
218 
getCharacterWidth()219     float getCharacterWidth() {
220         return current.getWidth();
221     }
222 
getCharacterHeight()223     float getCharacterHeight() {
224         return current.getHeight();
225     }
226 
nextCharacter()227     public boolean nextCharacter() {
228         if (current.isTail())
229             return false;
230         current = current.getNext();
231         return true;
232     }
233 
getCharacterSetPage()234     public int getCharacterSetPage() {
235         return current.getBitmapChar().getPage();
236     }
237 
getQuad()238     public LetterQuad getQuad() {
239         return current;
240     }
241 
rewind()242     public void rewind() {
243         current = head;
244     }
245 
invalidate()246     public void invalidate() {
247         invalidate(head);
248     }
249 
invalidate(LetterQuad cursor)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 
getScale()260     float getScale() {
261         return block.getSize() / font.getCharSet().getRenderedSize();
262     }
263 
isPrintable()264     public boolean isPrintable() {
265         return current.getBitmapChar() != null;
266     }
267 
getTotalWidth()268     float getTotalWidth() {
269         validateSize();
270         return totalWidth;
271     }
272 
getTotalHeight()273     float getTotalHeight() {
274         validateSize();
275         return totalHeight;
276     }
277 
validateSize()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      */
setStyle(int start, int end, int style)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      */
setColor( ColorRGBA color )308     void setColor( ColorRGBA color ) {
309         baseColor = color;
310         setColor( 0, block.getText().length(), color );
311     }
312 
getBaseColor()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      */
setColor(int start, int end, ColorRGBA color)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 }