Letters.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. /*
  2. * Copyright (c) 2009-2012 jMonkeyEngine
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are
  7. * met:
  8. *
  9. * * Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. *
  12. * * Redistributions in binary form must reproduce the above copyright
  13. * notice, this list of conditions and the following disclaimer in the
  14. * documentation and/or other materials provided with the distribution.
  15. *
  16. * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
  17. * may be used to endorse or promote products derived from this software
  18. * without specific prior written permission.
  19. *
  20. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  22. * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  23. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  24. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  25. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  26. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  27. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  28. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  29. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  30. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31. */
  32. package com.jme3.font;
  33. import com.jme3.font.BitmapFont.Align;
  34. import com.jme3.font.BitmapFont.VAlign;
  35. import com.jme3.font.ColorTags.Range;
  36. import com.jme3.math.ColorRGBA;
  37. import java.util.LinkedList;
  38. /**
  39. * Manage and align LetterQuads
  40. * @author YongHoon
  41. */
  42. class Letters {
  43. private final LetterQuad head;
  44. private final LetterQuad tail;
  45. private final BitmapFont font;
  46. private LetterQuad current;
  47. private StringBlock block;
  48. private float totalWidth;
  49. private float totalHeight;
  50. private ColorTags colorTags = new ColorTags();
  51. private ColorRGBA baseColor = null;
  52. private float baseAlpha = -1;
  53. private String plainText;
  54. Letters(BitmapFont font, StringBlock bound, boolean rightToLeft) {
  55. final String text = bound.getText();
  56. this.block = bound;
  57. this.font = font;
  58. head = new LetterQuad(font, rightToLeft);
  59. tail = new LetterQuad(font, rightToLeft);
  60. setText(text);
  61. }
  62. void setText(final String text) {
  63. colorTags.setText(text);
  64. plainText = colorTags.getPlainText();
  65. head.setNext(tail);
  66. tail.setPrevious(head);
  67. current = head;
  68. if (text != null && plainText.length() > 0) {
  69. LetterQuad l = head;
  70. for (int i = 0; i < plainText.length(); i++) {
  71. l = l.addNextCharacter(plainText.charAt(i));
  72. if (baseColor != null) {
  73. // Give the letter a default color if
  74. // one has been provided.
  75. l.setColor( baseColor );
  76. }
  77. }
  78. }
  79. LinkedList<Range> ranges = colorTags.getTags();
  80. if (!ranges.isEmpty()) {
  81. for (int i = 0; i < ranges.size()-1; i++) {
  82. Range start = ranges.get(i);
  83. Range end = ranges.get(i+1);
  84. setColor(start.start, end.start, start.color);
  85. }
  86. Range end = ranges.getLast();
  87. setColor(end.start, plainText.length(), end.color);
  88. }
  89. invalidate();
  90. }
  91. LetterQuad getHead() {
  92. return head;
  93. }
  94. LetterQuad getTail() {
  95. return tail;
  96. }
  97. void update() {
  98. LetterQuad l = head;
  99. int lineCount = 1;
  100. BitmapCharacter ellipsis = font.getCharSet().getCharacter(block.getEllipsisChar());
  101. float ellipsisWidth = ellipsis!=null? ellipsis.getWidth()*getScale(): 0;
  102. while (!l.isTail()) {
  103. if (l.isInvalid()) {
  104. l.update(block);
  105. if (l.isInvalid(block)) {
  106. switch (block.getLineWrapMode()) {
  107. case Character:
  108. lineWrap(l);
  109. lineCount++;
  110. break;
  111. case Word:
  112. if (!l.isBlank()) {
  113. // search last blank character before this word
  114. LetterQuad blank = l;
  115. while (!blank.isBlank()) {
  116. if (blank.isLineStart() || blank.isHead()) {
  117. lineWrap(l);
  118. lineCount++;
  119. blank = null;
  120. break;
  121. }
  122. blank = blank.getPrevious();
  123. }
  124. if (blank != null) {
  125. blank.setEndOfLine();
  126. lineCount++;
  127. while (blank != l) {
  128. blank = blank.getNext();
  129. blank.invalidate();
  130. blank.update(block);
  131. }
  132. }
  133. }
  134. break;
  135. case NoWrap:
  136. LetterQuad cursor = l.getPrevious();
  137. while (cursor.isInvalid(block, ellipsisWidth) && !cursor.isLineStart()) {
  138. cursor = cursor.getPrevious();
  139. }
  140. cursor.setBitmapChar(ellipsis);
  141. cursor.update(block);
  142. cursor = cursor.getNext();
  143. while (!cursor.isTail() && !cursor.isLineFeed()) {
  144. cursor.setBitmapChar(null);
  145. cursor.update(block);
  146. cursor = cursor.getNext();
  147. }
  148. break;
  149. case Clip:
  150. // Clip the character that falls out of bounds
  151. l.clip(block);
  152. // Clear the rest up to the next line feed.
  153. for( LetterQuad q = l.getNext(); !q.isTail() && !q.isLineFeed(); q = q.getNext() ) {
  154. q.setBitmapChar(null);
  155. q.update(block);
  156. }
  157. break;
  158. }
  159. }
  160. } else if (current.isInvalid(block)) {
  161. invalidate(current);
  162. }
  163. if (l.isEndOfLine()) {
  164. lineCount++;
  165. }
  166. l = l.getNext();
  167. }
  168. align();
  169. block.setLineCount(lineCount);
  170. rewind();
  171. }
  172. private void align() {
  173. final Align alignment = block.getAlignment();
  174. final VAlign valignment = block.getVerticalAlignment();
  175. if (block.getTextBox() == null || (alignment == Align.Left && valignment == VAlign.Top))
  176. return;
  177. LetterQuad cursor = tail.getPrevious();
  178. cursor.setEndOfLine();
  179. final float width = block.getTextBox().width;
  180. final float height = block.getTextBox().height;
  181. float lineWidth = 0;
  182. float gapX = 0;
  183. float gapY = 0;
  184. validateSize();
  185. if (totalHeight < height) { // align vertically only for no overflow
  186. switch (valignment) {
  187. case Top:
  188. gapY = 0;
  189. break;
  190. case Center:
  191. gapY = (height-totalHeight)*0.5f;
  192. break;
  193. case Bottom:
  194. gapY = height-totalHeight;
  195. break;
  196. }
  197. }
  198. while (!cursor.isHead()) {
  199. if (cursor.isEndOfLine()) {
  200. lineWidth = cursor.getX1()-block.getTextBox().x;
  201. if (alignment == Align.Center) {
  202. gapX = (width-lineWidth)/2;
  203. } else if (alignment == Align.Right) {
  204. gapX = width-lineWidth;
  205. } else {
  206. gapX = 0;
  207. }
  208. }
  209. cursor.setAlignment(gapX, gapY);
  210. cursor = cursor.getPrevious();
  211. }
  212. }
  213. private void lineWrap(LetterQuad l) {
  214. if (l.isHead() || l.isBlank())
  215. return;
  216. l.getPrevious().setEndOfLine();
  217. l.invalidate();
  218. l.update(block); // TODO: update from l
  219. }
  220. float getCharacterX0() {
  221. return current.getX0();
  222. }
  223. float getCharacterY0() {
  224. return current.getY0();
  225. }
  226. float getCharacterX1() {
  227. return current.getX1();
  228. }
  229. float getCharacterY1() {
  230. return current.getY1();
  231. }
  232. float getCharacterAlignX() {
  233. return current.getAlignX();
  234. }
  235. float getCharacterAlignY() {
  236. return current.getAlignY();
  237. }
  238. float getCharacterWidth() {
  239. return current.getWidth();
  240. }
  241. float getCharacterHeight() {
  242. return current.getHeight();
  243. }
  244. public boolean nextCharacter() {
  245. if (current.isTail())
  246. return false;
  247. current = current.getNext();
  248. return true;
  249. }
  250. public int getCharacterSetPage() {
  251. return current.getBitmapChar().getPage();
  252. }
  253. public LetterQuad getQuad() {
  254. return current;
  255. }
  256. public void rewind() {
  257. current = head;
  258. }
  259. public void invalidate() {
  260. invalidate(head);
  261. }
  262. public void invalidate(LetterQuad cursor) {
  263. totalWidth = -1;
  264. totalHeight = -1;
  265. while (!cursor.isTail() && !cursor.isInvalid()) {
  266. cursor.invalidate();
  267. cursor = cursor.getNext();
  268. }
  269. }
  270. float getScale() {
  271. return block.getSize() / font.getCharSet().getRenderedSize();
  272. }
  273. public boolean isPrintable() {
  274. return current.getBitmapChar() != null;
  275. }
  276. float getTotalWidth() {
  277. validateSize();
  278. return totalWidth;
  279. }
  280. float getTotalHeight() {
  281. validateSize();
  282. return totalHeight;
  283. }
  284. void validateSize() {
  285. if (totalWidth < 0) {
  286. LetterQuad l = head;
  287. while (!l.isTail()) {
  288. totalWidth = Math.max(totalWidth, l.getX1());
  289. l = l.getNext();
  290. totalHeight = Math.max(totalHeight, -l.getY1());
  291. }
  292. }
  293. }
  294. /**
  295. * @param start start index to set style. inclusive.
  296. * @param end end index to set style. EXCLUSIVE.
  297. * @param style
  298. */
  299. void setStyle(int start, int end, int style) {
  300. LetterQuad cursor = head.getNext();
  301. while (!cursor.isTail()) {
  302. if (cursor.getIndex() >= start && cursor.getIndex() < end) {
  303. cursor.setStyle(style);
  304. }
  305. cursor = cursor.getNext();
  306. }
  307. }
  308. /**
  309. * Sets the base color for all new letter quads and resets
  310. * the color of existing letter quads.
  311. */
  312. void setColor( ColorRGBA color ) {
  313. baseColor = color;
  314. colorTags.setBaseColor(color);
  315. setColor( 0, block.getText().length(), color );
  316. }
  317. ColorRGBA getBaseColor() {
  318. return baseColor;
  319. }
  320. /**
  321. * @param start start index to set style. inclusive.
  322. * @param end end index to set style. EXCLUSIVE.
  323. * @param color
  324. */
  325. void setColor(int start, int end, ColorRGBA color) {
  326. LetterQuad cursor = head.getNext();
  327. while (!cursor.isTail()) {
  328. if (cursor.getIndex() >= start && cursor.getIndex() < end) {
  329. cursor.setColor(color);
  330. }
  331. cursor = cursor.getNext();
  332. }
  333. }
  334. float getBaseAlpha() {
  335. return baseAlpha;
  336. }
  337. void setBaseAlpha( float alpha ) { this.baseAlpha = alpha;
  338. colorTags.setBaseAlpha(alpha);
  339. if (alpha == -1) {
  340. alpha = baseColor != null ? baseColor.a : 1;
  341. }
  342. // Forward the new alpha to the letter quads
  343. LetterQuad cursor = head.getNext();
  344. while (!cursor.isTail()) {
  345. cursor.setAlpha(alpha);
  346. cursor = cursor.getNext();
  347. }
  348. // If the alpha was reset to "default", ie: -1
  349. // then the color tags are potentially reset and
  350. // we need to reapply them. This has to be done
  351. // second since it may override any alpha values
  352. // set above... but you still need to do the above
  353. // since non-color tagged text is treated differently
  354. // even if part of a color tagged string.
  355. if (baseAlpha == -1) {
  356. LinkedList<Range> ranges = colorTags.getTags();
  357. if (!ranges.isEmpty()) {
  358. for (int i = 0; i < ranges.size()-1; i++) {
  359. Range start = ranges.get(i);
  360. Range end = ranges.get(i+1);
  361. setColor(start.start, end.start, start.color);
  362. }
  363. Range end = ranges.getLast();
  364. setColor(end.start, plainText.length(), end.color);
  365. }
  366. }
  367. invalidate();
  368. }
  369. }