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      */
LetterQuad(BitmapFont font, boolean rightToLeft)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      */
LetterQuad(char c, LetterQuad prev)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 
addNextCharacter(char c)72     LetterQuad addNextCharacter(char c) {
73         LetterQuad n = new LetterQuad(c, this);
74         return n;
75     }
76 
getBitmapChar()77     BitmapCharacter getBitmapChar() {
78         return bitmapChar;
79     }
80 
getChar()81     char getChar() {
82         return c;
83     }
84 
getIndex()85     int getIndex() {
86         return index;
87     }
88 
getBound(StringBlock block)89     private Rectangle getBound(StringBlock block) {
90         if (block.getTextBox() != null) {
91             return block.getTextBox();
92         }
93         return UNBOUNDED;
94     }
95 
getPrevious()96     LetterQuad getPrevious() {
97         return previous;
98     }
99 
getNext()100     LetterQuad getNext() {
101         return next;
102     }
103 
getU0()104     public float getU0() {
105         return u0;
106     }
107 
getU1()108     float getU1() {
109         return u1;
110     }
111 
getV0()112     float getV0() {
113         return v0;
114     }
115 
getV1()116     float getV1() {
117         return v1;
118     }
119 
isInvalid()120     boolean isInvalid() {
121         return x0 == Integer.MIN_VALUE;
122     }
123 
isInvalid(StringBlock block)124     boolean isInvalid(StringBlock block) {
125         return isInvalid(block, 0);
126     }
127 
isInvalid(StringBlock block, float gap)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 
getX0()141     float getX0() {
142         return x0;
143     }
144 
getX1()145     float getX1() {
146         return x0+width;
147     }
148 
getNextX()149     float getNextX() {
150         return x0+xAdvance;
151     }
152 
getNextLine()153     float getNextLine() {
154         return lineY+LINE_DIR*font.getCharSet().getLineHeight() * sizeScale;
155     }
156 
getY0()157     float getY0() {
158         return y0;
159     }
160 
getY1()161     float getY1() {
162         return y0-height;
163     }
164 
getWidth()165     float getWidth() {
166         return width;
167     }
168 
getHeight()169     float getHeight() {
170         return height;
171     }
172 
insert(LetterQuad ins)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 
invalidate()181     void invalidate() {
182         eol = isLineFeed();
183         setBitmapChar(font.getCharSet().getCharacter(c, style));
184     }
185 
isTail()186     boolean isTail() {
187         return next == null;
188     }
189 
isHead()190     boolean isHead() {
191         return previous == null;
192     }
193 
194     /**
195      * @return next letter
196      */
remove()197     LetterQuad remove() {
198         this.previous.next = next;
199         this.next.previous = previous;
200         return next;
201     }
202 
setPrevious(LetterQuad before)203     void setPrevious(LetterQuad before) {
204         this.previous = before;
205     }
206 
setStyle(int style)207     void setStyle(int style) {
208         this.style = style;
209         invalidate();
210     }
211 
setColor(ColorRGBA color)212     void setColor(ColorRGBA color) {
213         this.colorInt = color.asIntRGBA();
214         invalidate();
215     }
216 
setBitmapChar(char c)217     void setBitmapChar(char c) {
218         BitmapCharacterSet charSet = font.getCharSet();
219         BitmapCharacter bm = charSet.getCharacter(c, style);
220         setBitmapChar(bm);
221     }
222 
setBitmapChar(BitmapCharacter bitmapChar)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 
setNext(LetterQuad next)246     void setNext(LetterQuad next) {
247         this.next = next;
248     }
249 
update(StringBlock block)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      */
setEndOfLine()340     void setEndOfLine() {
341         this.eol = true;
342     }
343 
isEndOfLine()344     boolean isEndOfLine() {
345         return eol;
346     }
347 
isLineWrap()348     boolean isLineWrap() {
349         return !isHead() && !isTail() && bitmapChar == null && c == Character.MIN_VALUE;
350     }
351 
computeLineY(StringBlock block)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 
isLineStart()363     boolean isLineStart() {
364         return x0 == 0 || (previous != null && previous.eol);
365     }
366 
isBlank()367     boolean isBlank() {
368         return c == ' ' || isTab();
369     }
370 
storeToArrays(float[] pos, float[] tc, short[] idx, byte[] colors, int quadIdx)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 
appendPositions(FloatBuffer fb)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 
appendPositions(ShortBuffer sb)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 
appendTexCoords(FloatBuffer fb)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 
appendColors(ByteBuffer bb)449     public void appendColors(ByteBuffer bb){
450         bb.putInt(colorInt);
451         bb.putInt(colorInt);
452         bb.putInt(colorInt);
453         bb.putInt(colorInt);
454     }
455 
appendIndices(ShortBuffer sb, int quadIndex)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
toString()471     public String toString() {
472         return String.valueOf(c);
473     }
474 
setAlignment(float alignX, float alignY)475     void setAlignment(float alignX, float alignY) {
476         this.alignX = alignX;
477         this.alignY = alignY;
478     }
479 
getAlignX()480     float getAlignX() {
481         return alignX;
482     }
483 
getAlignY()484     float getAlignY() {
485         return alignY;
486     }
487 
isLineFeed()488     boolean isLineFeed() {
489         return c == '\n';
490     }
491 
isTab()492     boolean isTab() {
493         return c == '\t';
494     }
495 
496 }
497