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 }