1 /* 2 * Copyright (c) 2009-2010 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 33 package com.jme3.font; 34 35 import com.jme3.export.*; 36 import com.jme3.material.Material; 37 import java.io.IOException; 38 39 /** 40 * Represents a font within jME that is generated with the AngelCode Bitmap Font Generator 41 * @author dhdd 42 */ 43 public class BitmapFont implements Savable { 44 45 /** 46 * Specifies horizontal alignment for text. 47 * 48 * @see BitmapText#setAlignment(com.jme3.font.BitmapFont.Align) 49 */ 50 public enum Align { 51 52 /** 53 * Align text on the left of the text block 54 */ 55 Left, 56 57 /** 58 * Align text in the center of the text block 59 */ 60 Center, 61 62 /** 63 * Align text on the right of the text block 64 */ 65 Right 66 } 67 68 /** 69 * Specifies vertical alignment for text. 70 * 71 * @see BitmapText#setVerticalAlignment(com.jme3.font.BitmapFont.VAlign) 72 */ 73 public enum VAlign { 74 /** 75 * Align text on the top of the text block 76 */ 77 Top, 78 79 /** 80 * Align text in the center of the text block 81 */ 82 Center, 83 84 /** 85 * Align text at the bottom of the text block 86 */ 87 Bottom 88 } 89 90 private BitmapCharacterSet charSet; 91 private Material[] pages; 92 BitmapFont()93 public BitmapFont() { 94 } 95 createLabel(String content)96 public BitmapText createLabel(String content){ 97 BitmapText label = new BitmapText(this); 98 label.setSize(getCharSet().getRenderedSize()); 99 label.setText(content); 100 return label; 101 } 102 getPreferredSize()103 public float getPreferredSize(){ 104 return getCharSet().getRenderedSize(); 105 } 106 setCharSet(BitmapCharacterSet charSet)107 public void setCharSet(BitmapCharacterSet charSet) { 108 this.charSet = charSet; 109 } 110 setPages(Material[] pages)111 public void setPages(Material[] pages) { 112 this.pages = pages; 113 charSet.setPageSize(pages.length); 114 } 115 getPage(int index)116 public Material getPage(int index) { 117 return pages[index]; 118 } 119 getPageSize()120 public int getPageSize() { 121 return pages.length; 122 } 123 getCharSet()124 public BitmapCharacterSet getCharSet() { 125 return charSet; 126 } 127 128 /** 129 * Gets the line height of a StringBlock. 130 * @param sb 131 * @return 132 */ getLineHeight(StringBlock sb)133 public float getLineHeight(StringBlock sb) { 134 return charSet.getLineHeight() * (sb.getSize() / charSet.getRenderedSize()); 135 } 136 getCharacterAdvance(char curChar, char nextChar, float size)137 public float getCharacterAdvance(char curChar, char nextChar, float size){ 138 BitmapCharacter c = charSet.getCharacter(curChar); 139 if (c == null) 140 return 0f; 141 142 float advance = size * c.getXAdvance(); 143 advance += c.getKerning(nextChar) * size; 144 return advance; 145 } 146 findKerningAmount(int newLineLastChar, int nextChar)147 private int findKerningAmount(int newLineLastChar, int nextChar) { 148 BitmapCharacter c = charSet.getCharacter(newLineLastChar); 149 if (c == null) 150 return 0; 151 return c.getKerning(nextChar); 152 } 153 154 @Override write(JmeExporter ex)155 public void write(JmeExporter ex) throws IOException { 156 OutputCapsule oc = ex.getCapsule(this); 157 oc.write(charSet, "charSet", null); 158 oc.write(pages, "pages", null); 159 } 160 161 @Override read(JmeImporter im)162 public void read(JmeImporter im) throws IOException { 163 InputCapsule ic = im.getCapsule(this); 164 charSet = (BitmapCharacterSet) ic.readSavable("charSet", null); 165 Savable[] pagesSavable = ic.readSavableArray("pages", null); 166 pages = new Material[pagesSavable.length]; 167 System.arraycopy(pagesSavable, 0, pages, 0, pages.length); 168 } 169 getLineWidth(CharSequence text)170 public float getLineWidth(CharSequence text){ 171 172 // This method will probably always be a bit of a maintenance 173 // nightmare since it basis its calculation on a different 174 // routine than the Letters class. The ideal situation would 175 // be to abstract out letter position and size into its own 176 // class that both BitmapFont and Letters could use for 177 // positioning. 178 // If getLineWidth() here ever again returns a different value 179 // than Letters does with the same text then it might be better 180 // just to create a Letters object for the sole purpose of 181 // getting a text size. It's less efficient but at least it 182 // would be accurate. 183 184 // And here I am mucking around in here again... 185 // 186 // A font character has a few values that are pertinent to the 187 // line width: 188 // xOffset 189 // xAdvance 190 // kerningAmount(nextChar) 191 // 192 // The way BitmapText ultimately works is that the first character 193 // starts with xOffset included (ie: it is rendered at -xOffset). 194 // Its xAdvance is wider to accomodate that initial offset. 195 // The cursor position is advanced by xAdvance each time. 196 // 197 // So, a width should be calculated in a similar way. Start with 198 // -xOffset + xAdvance for the first character and then each subsequent 199 // character is just xAdvance more 'width'. 200 // 201 // The kerning amount from one character to the next affects the 202 // cursor position of that next character and thus the ultimate width 203 // and so must be factored in also. 204 205 float lineWidth = 0f; 206 float maxLineWidth = 0f; 207 char lastChar = 0; 208 boolean firstCharOfLine = true; 209 // float sizeScale = (float) block.getSize() / charSet.getRenderedSize(); 210 float sizeScale = 1f; 211 for (int i = 0; i < text.length(); i++){ 212 char theChar = text.charAt(i); 213 if (theChar == '\n'){ 214 maxLineWidth = Math.max(maxLineWidth, lineWidth); 215 lineWidth = 0f; 216 firstCharOfLine = true; 217 continue; 218 } 219 BitmapCharacter c = charSet.getCharacter((int) theChar); 220 if (c != null){ 221 if (theChar == '\\' && i<text.length()-1 && text.charAt(i+1)=='#'){ 222 if (i+5<text.length() && text.charAt(i+5)=='#'){ 223 i+=5; 224 continue; 225 }else if (i+8<text.length() && text.charAt(i+8)=='#'){ 226 i+=8; 227 continue; 228 } 229 } 230 if (!firstCharOfLine){ 231 lineWidth += findKerningAmount(lastChar, theChar) * sizeScale; 232 } else { 233 // The first character needs to add in its xOffset but it 234 // is the only one... and negative offsets = postive width 235 // because we're trying to account for the part that hangs 236 // over the left. So we subtract. 237 lineWidth -= c.getXOffset() * sizeScale; 238 firstCharOfLine = false; 239 } 240 float xAdvance = c.getXAdvance() * sizeScale; 241 242 // If this is the last character, then we really should have 243 // only add its width. The advance may include extra spacing 244 // that we don't care about. 245 if (i == text.length() - 1) { 246 lineWidth += c.getWidth() * sizeScale; 247 248 // Since theh width includes the xOffset then we need 249 // to take it out again by adding it, ie: offset the width 250 // we just added by the appropriate amount. 251 lineWidth += c.getXOffset() * sizeScale; 252 } else { 253 lineWidth += xAdvance; 254 } 255 } 256 } 257 return Math.max(maxLineWidth, lineWidth); 258 } 259 260 261 /** 262 * Merge two fonts. 263 * If two font have the same style, merge will fail. 264 * @param styleSet Style must be assigned to this. 265 * @author Yonghoon 266 */ merge(BitmapFont newFont)267 public void merge(BitmapFont newFont) { 268 charSet.merge(newFont.charSet); 269 final int size1 = this.pages.length; 270 final int size2 = newFont.pages.length; 271 272 Material[] tmp = new Material[size1+size2]; 273 System.arraycopy(this.pages, 0, tmp, 0, size1); 274 System.arraycopy(newFont.pages, 0, tmp, size1, size2); 275 276 this.pages = tmp; 277 278 // this.pages = Arrays.copyOf(this.pages, size1+size2); 279 // System.arraycopy(newFont.pages, 0, this.pages, size1, size2); 280 } 281 setStyle(int style)282 public void setStyle(int style) { 283 charSet.setStyle(style); 284 } 285 286 }