1 /******************************************************************************* 2 * Copyright 2011 See AUTHORS file. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 ******************************************************************************/ 16 17 package com.badlogic.gdx.tools.bmfont; 18 19 import com.badlogic.gdx.files.FileHandle; 20 import com.badlogic.gdx.graphics.Pixmap; 21 import com.badlogic.gdx.graphics.PixmapIO; 22 import com.badlogic.gdx.graphics.g2d.BitmapFont.BitmapFontData; 23 import com.badlogic.gdx.graphics.g2d.BitmapFont.Glyph; 24 import com.badlogic.gdx.graphics.g2d.PixmapPacker.Page; 25 import com.badlogic.gdx.tools.hiero.Hiero; 26 import com.badlogic.gdx.utils.Array; 27 28 /** A utility to output BitmapFontData to a FNT file. This can be useful for caching the result from TrueTypeFont, for faster load 29 * times. 30 * <p> 31 * The font file format is from the AngelCodeFont BMFont tool. 32 * <p> 33 * Output is nearly identical to the FreeType settting in the {@link Hiero} tool. BitmapFontWriter gives more flexibility, eg 34 * borders and shadows can be used. Hiero is able to avoid outputting the same glyph image more than once if multiple character 35 * codes have the exact same glyph. 36 * @author mattdesl AKA davedes */ 37 public class BitmapFontWriter { 38 39 /** The output format. */ 40 public static enum OutputFormat { 41 42 /** AngelCodeFont text format */ 43 Text, 44 /** AngelCodeFont XML format */ 45 XML; 46 } 47 48 /** The output format */ 49 private static OutputFormat format = OutputFormat.Text; 50 51 /** Sets the AngelCodeFont output format for subsequent writes; can be text (for LibGDX) or XML (for other engines, like 52 * Pixi.js). 53 * 54 * @param fmt the output format to use */ setOutputFormat(OutputFormat fmt)55 public static void setOutputFormat (OutputFormat fmt) { 56 if (fmt == null) throw new NullPointerException("format cannot be null"); 57 format = fmt; 58 } 59 60 /** Returns the currently used output format. 61 * @return the output format */ getOutputFormat()62 public static OutputFormat getOutputFormat () { 63 return format; 64 } 65 66 /** The Padding parameter for FontInfo. */ 67 public static class Padding { 68 public int up, down, left, right; 69 Padding()70 public Padding () { 71 } 72 Padding(int up, int down, int left, int right)73 public Padding (int up, int down, int left, int right) { 74 this.up = up; 75 this.down = down; 76 this.left = left; 77 this.right = right; 78 } 79 } 80 81 /** The spacing parameter for FontInfo. */ 82 public static class Spacing { 83 public int horizontal, vertical; 84 } 85 86 /** The font "info" line; everything except padding is ignored by LibGDX's BitmapFont reader, it is otherwise just useful for 87 * clean and organized output. */ 88 public static class FontInfo { 89 /** Face name */ 90 public String face; 91 /** Font size (pt) */ 92 public int size = 12; 93 /** Whether the font is bold */ 94 public boolean bold; 95 /** Whether the font is italic */ 96 public boolean italic; 97 /** The charset; or null/empty for default */ 98 public String charset; 99 /** Whether the font uses unicode glyphs */ 100 public boolean unicode = true; 101 /** Stretch for height; default to 100% */ 102 public int stretchH = 100; 103 /** Whether smoothing is applied */ 104 public boolean smooth = true; 105 /** Amount of anti-aliasing that was applied to the font */ 106 public int aa = 2; 107 /** Padding that was applied to the font */ 108 public Padding padding = new Padding(); 109 /** Horizontal/vertical spacing that was applied to font */ 110 public Spacing spacing = new Spacing(); 111 public int outline = 0; 112 FontInfo()113 public FontInfo () { 114 } 115 FontInfo(String face, int size)116 public FontInfo (String face, int size) { 117 this.face = face; 118 this.size = size; 119 } 120 } 121 quote(Object params)122 private static String quote (Object params) { 123 return quote(params, false); 124 } 125 quote(Object params, boolean spaceAfter)126 private static String quote (Object params, boolean spaceAfter) { 127 if (BitmapFontWriter.getOutputFormat() == OutputFormat.XML) 128 return "\"" + params.toString().trim() + "\"" + (spaceAfter ? " " : ""); 129 else 130 return params.toString(); 131 } 132 133 /** Writes the given BitmapFontData to a file, using the specified <tt>pageRefs</tt> strings as the image paths for each 134 * texture page. The glyphs in BitmapFontData have a "page" id, which references the index of the pageRef you specify here. 135 * 136 * The FontInfo parameter is useful for cleaner output; such as including a size and font face name hint. However, it can be 137 * null to use default values. LibGDX ignores most of the "info" line when reading back fonts, only padding is used. Padding 138 * also affects the size, location, and offset of the glyphs that are output. 139 * 140 * Likewise, the scaleW and scaleH are only for cleaner output. They are currently ignored by LibGDX's reader. For maximum 141 * compatibility with other BMFont tools, you should use the width and height of your texture pages (each page should be the 142 * same size). 143 * 144 * @param fontData the bitmap font 145 * @param pageRefs the references to each texture page image file, generally in the same folder as outFntFile 146 * @param outFntFile the font file to save to (typically ends with '.fnt') 147 * @param info the optional info for the file header; can be null 148 * @param scaleW the width of your texture pages 149 * @param scaleH the height of your texture pages */ writeFont(BitmapFontData fontData, String[] pageRefs, FileHandle outFntFile, FontInfo info, int scaleW, int scaleH)150 public static void writeFont (BitmapFontData fontData, String[] pageRefs, FileHandle outFntFile, FontInfo info, int scaleW, 151 int scaleH) { 152 if (info == null) { 153 info = new FontInfo(); 154 info.face = outFntFile.nameWithoutExtension(); 155 } 156 157 int lineHeight = (int)fontData.lineHeight; 158 int pages = pageRefs.length; 159 int packed = 0; 160 int base = (int)((fontData.capHeight) + (fontData.flipped ? -fontData.ascent : fontData.ascent)); 161 OutputFormat fmt = BitmapFontWriter.getOutputFormat(); 162 boolean xml = fmt == OutputFormat.XML; 163 164 StringBuilder buf = new StringBuilder(); 165 166 if (xml) { 167 buf.append("<font>\n"); 168 } 169 String xmlOpen = xml ? "\t<" : ""; 170 String xmlCloseSelf = xml ? "/>" : ""; 171 String xmlTab = xml ? "\t" : ""; 172 String xmlClose = xml ? ">" : ""; 173 174 String xmlQuote = xml ? "\"" : ""; 175 String alphaChnlParams = xml ? " alphaChnl=\"0\" redChnl=\"0\" greenChnl=\"0\" blueChnl=\"0\"" 176 : " alphaChnl=0 redChnl=0 greenChnl=0 blueChnl=0"; 177 178 // INFO LINE 179 buf.append(xmlOpen).append("info face=\"").append(info.face == null ? "" : info.face.replaceAll("\"", "'")) 180 .append("\" size=").append(quote(info.size)).append(" bold=").append(quote(info.bold ? 1 : 0)).append(" italic=") 181 .append(quote(info.italic ? 1 : 0)).append(" charset=\"").append(info.charset == null ? "" : info.charset) 182 .append("\" unicode=").append(quote(info.unicode ? 1 : 0)).append(" stretchH=").append(quote(info.stretchH)) 183 .append(" smooth=").append(quote(info.smooth ? 1 : 0)).append(" aa=").append(quote(info.aa)).append(" padding=") 184 .append(xmlQuote).append(info.padding.up).append(",").append(info.padding.down).append(",").append(info.padding.left) 185 .append(",").append(info.padding.right).append(xmlQuote).append(" spacing=").append(xmlQuote) 186 .append(info.spacing.horizontal).append(",").append(info.spacing.vertical).append(xmlQuote).append(xmlCloseSelf) 187 .append("\n"); 188 189 // COMMON line 190 buf.append(xmlOpen).append("common lineHeight=").append(quote(lineHeight)).append(" base=").append(quote(base)) 191 .append(" scaleW=").append(quote(scaleW)).append(" scaleH=").append(quote(scaleH)).append(" pages=").append(quote(pages)) 192 .append(" packed=").append(quote(packed)).append(alphaChnlParams).append(xmlCloseSelf).append("\n"); 193 194 if (xml) buf.append("\t<pages>\n"); 195 196 // PAGES 197 for (int i = 0; i < pageRefs.length; i++) { 198 buf.append(xmlTab).append(xmlOpen).append("page id=").append(quote(i)).append(" file=\"").append(pageRefs[i]) 199 .append("\"").append(xmlCloseSelf).append("\n"); 200 } 201 202 if (xml) buf.append("\t</pages>\n"); 203 204 // CHARS 205 Array<Glyph> glyphs = new Array<Glyph>(256); 206 for (int i = 0; i < fontData.glyphs.length; i++) { 207 if (fontData.glyphs[i] == null) continue; 208 209 for (int j = 0; j < fontData.glyphs[i].length; j++) { 210 if (fontData.glyphs[i][j] != null) { 211 glyphs.add(fontData.glyphs[i][j]); 212 } 213 } 214 } 215 216 buf.append(xmlOpen).append("chars count=").append(quote(glyphs.size)).append(xmlClose).append("\n"); 217 218 int padLeft = 0, padRight = 0, padTop = 0, padX = 0, padY = 0; 219 if (info != null) { 220 padTop = info.padding.up; 221 padLeft = info.padding.left; 222 padRight = info.padding.right; 223 padX = padLeft + padRight; 224 padY = info.padding.up + info.padding.down; 225 } 226 227 // CHAR definitions 228 for (int i = 0; i < glyphs.size; i++) { 229 Glyph g = glyphs.get(i); 230 boolean empty = g.width == 0 || g.height == 0; 231 buf.append(xmlTab).append(xmlOpen).append("char id=").append(quote(String.format("%-6s", g.id), true)).append("x=") 232 .append(quote(String.format("%-5s", empty ? 0 : g.srcX - padLeft), true)).append("y=") 233 .append(quote(String.format("%-5s", empty ? 0 : g.srcY - padRight), true)).append("width=") 234 .append(quote(String.format("%-5s", empty ? 0 : g.width + padX), true)).append("height=") 235 .append(quote(String.format("%-5s", empty ? 0 : g.height + padY), true)).append("xoffset=") 236 .append(quote(String.format("%-5s", g.xoffset - padLeft), true)).append("yoffset=") 237 .append( 238 quote(String.format("%-5s", fontData.flipped ? g.yoffset + padTop : -(g.height + (g.yoffset + padTop))), true)) 239 .append("xadvance=").append(quote(String.format("%-5s", g.xadvance), true)).append("page=") 240 .append(quote(String.format("%-5s", g.page), true)).append("chnl=").append(quote(0, true)).append(xmlCloseSelf) 241 .append("\n"); 242 } 243 244 if (xml) buf.append("\t</chars>\n"); 245 246 // KERNINGS 247 int kernCount = 0; 248 StringBuilder kernBuf = new StringBuilder(); 249 for (int i = 0; i < glyphs.size; i++) { 250 for (int j = 0; j < glyphs.size; j++) { 251 Glyph first = glyphs.get(i); 252 Glyph second = glyphs.get(j); 253 int kern = first.getKerning((char)second.id); 254 if (kern != 0) { 255 kernCount++; 256 kernBuf.append(xmlTab).append(xmlOpen).append("kerning first=").append(quote(first.id)).append(" second=") 257 .append(quote(second.id)).append(" amount=").append(quote(kern, true)).append(xmlCloseSelf).append("\n"); 258 } 259 } 260 } 261 262 // KERN info 263 buf.append(xmlOpen).append("kernings count=").append(quote(kernCount)).append(xmlClose).append("\n"); 264 buf.append(kernBuf); 265 266 if (xml) { 267 buf.append("\t</kernings>\n"); 268 buf.append("</font>"); 269 } 270 271 String charset = info.charset; 272 if (charset != null && charset.length() == 0) charset = null; 273 274 outFntFile.writeString(buf.toString(), false, charset); 275 } 276 277 /** A utility method which writes the given font data to a file. 278 * 279 * The specified pixmaps are written to the parent directory of <tt>outFntFile</tt>, using that file's name without an 280 * extension for the PNG file name(s). 281 * 282 * The specified FontInfo is optional, and can be null. 283 * 284 * Typical usage looks like this: 285 * 286 * <pre> 287 * BitmapFontWriter.writeFont(myFontData, myFontPixmaps, Gdx.files.external("fonts/output.fnt"), new FontInfo("Arial", 16)); 288 * </pre> 289 * 290 * @param fontData the font data 291 * @param pages the pixmaps to write as PNGs 292 * @param outFntFile the output file for the font definition 293 * @param info the optional font info for the header file, can be null */ writeFont(BitmapFontData fontData, Pixmap[] pages, FileHandle outFntFile, FontInfo info)294 public static void writeFont (BitmapFontData fontData, Pixmap[] pages, FileHandle outFntFile, FontInfo info) { 295 String[] pageRefs = writePixmaps(pages, outFntFile.parent(), outFntFile.nameWithoutExtension()); 296 297 // write the font data 298 writeFont(fontData, pageRefs, outFntFile, info, pages[0].getWidth(), pages[0].getHeight()); 299 } 300 301 /** A utility method to write the given array of pixmaps to the given output directory, with the specified file name. If the 302 * pages array is of length 1, then the resulting file ref will look like: "fileName.png". 303 * 304 * If the pages array is greater than length 1, the resulting file refs will be appended with "_N", such as "fileName_0.png", 305 * "fileName_1.png", "fileName_2.png" etc. 306 * 307 * The returned string array can then be passed to the <tt>writeFont</tt> method. 308 * 309 * Note: None of the pixmaps will be disposed. 310 * 311 * @param pages the pages of pixmap data to write 312 * @param outputDir the output directory 313 * @param fileName the file names for the output images 314 * @return the array of string references to be used with <tt>writeFont</tt> */ writePixmaps(Pixmap[] pages, FileHandle outputDir, String fileName)315 public static String[] writePixmaps (Pixmap[] pages, FileHandle outputDir, String fileName) { 316 if (pages == null || pages.length == 0) throw new IllegalArgumentException("no pixmaps supplied to BitmapFontWriter.write"); 317 318 String[] pageRefs = new String[pages.length]; 319 320 for (int i = 0; i < pages.length; i++) { 321 String ref = pages.length == 1 ? (fileName + ".png") : (fileName + "_" + i + ".png"); 322 323 // the ref for this image 324 pageRefs[i] = ref; 325 326 // write the PNG in that directory 327 PixmapIO.writePNG(outputDir.child(ref), pages[i]); 328 } 329 return pageRefs; 330 } 331 332 /** A convenience method to write pixmaps by page; typically returned from a PixmapPacker when used alongside 333 * FreeTypeFontGenerator. 334 * 335 * @param pages the pages containing the Pixmaps 336 * @param outputDir the output directory 337 * @param fileName the file name 338 * @return the file refs */ writePixmaps(Array<Page> pages, FileHandle outputDir, String fileName)339 public static String[] writePixmaps (Array<Page> pages, FileHandle outputDir, String fileName) { 340 Pixmap[] pix = new Pixmap[pages.size]; 341 for (int i = 0; i < pages.size; i++) { 342 pix[i] = pages.get(i).getPixmap(); 343 } 344 return writePixmaps(pix, outputDir, fileName); 345 } 346 } 347