Home | History | Annotate | Download | only in g2d
      1 /*
      2  * Copyright (c) 2008-2010, Matthias Mann
      3  *
      4  * All rights reserved.
      5  *
      6  * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
      7  * conditions are met:
      8  *
      9  * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
     10  * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
     11  * disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Matthias Mann nor
     12  * the names of its contributors may be used to endorse or promote products derived from this software without specific prior
     13  * written permission.
     14  *
     15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
     16  * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
     17  * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     19  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     20  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     21  */
     22 
     23 package com.badlogic.gdx.graphics.g2d;
     24 
     25 import java.io.BufferedReader;
     26 import java.io.InputStreamReader;
     27 import java.util.StringTokenizer;
     28 
     29 import com.badlogic.gdx.Gdx;
     30 import com.badlogic.gdx.files.FileHandle;
     31 import com.badlogic.gdx.graphics.Color;
     32 import com.badlogic.gdx.graphics.Texture;
     33 import com.badlogic.gdx.graphics.Texture.TextureFilter;
     34 import com.badlogic.gdx.graphics.g2d.GlyphLayout.GlyphRun;
     35 import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
     36 import com.badlogic.gdx.utils.Array;
     37 import com.badlogic.gdx.utils.Disposable;
     38 import com.badlogic.gdx.utils.FloatArray;
     39 import com.badlogic.gdx.utils.GdxRuntimeException;
     40 import com.badlogic.gdx.utils.StreamUtils;
     41 
     42 /** Renders bitmap fonts. The font consists of 2 files: an image file or {@link TextureRegion} containing the glyphs and a file in
     43  * the AngleCode BMFont text format that describes where each glyph is on the image.
     44  * <p>
     45  * Text is drawn using a {@link Batch}. Text can be cached in a {@link BitmapFontCache} for faster rendering of static text, which
     46  * saves needing to compute the location of each glyph each frame.
     47  * <p>
     48  * * The texture for a BitmapFont loaded from a file is managed. {@link #dispose()} must be called to free the texture when no
     49  * longer needed. A BitmapFont loaded using a {@link TextureRegion} is managed if the region's texture is managed. Disposing the
     50  * BitmapFont disposes the region's texture, which may not be desirable if the texture is still being used elsewhere.
     51  * <p>
     52  * The code was originally based on Matthias Mann's TWL BitmapFont class. Thanks for sharing, Matthias! :)
     53  * @author Nathan Sweet
     54  * @author Matthias Mann */
     55 public class BitmapFont implements Disposable {
     56 	static private final int LOG2_PAGE_SIZE = 9;
     57 	static private final int PAGE_SIZE = 1 << LOG2_PAGE_SIZE;
     58 	static private final int PAGES = 0x10000 / PAGE_SIZE;
     59 
     60 	final BitmapFontData data;
     61 	Array<TextureRegion> regions;
     62 	private final BitmapFontCache cache;
     63 	private boolean flipped;
     64 	boolean integer;
     65 	private boolean ownsTexture;
     66 
     67 	/** Creates a BitmapFont using the default 15pt Arial font included in the libgdx JAR file. This is convenient to easily
     68 	 * display text without bothering without generating a bitmap font yourself. */
     69 	public BitmapFont () {
     70 		this(Gdx.files.classpath("com/badlogic/gdx/utils/arial-15.fnt"), Gdx.files.classpath("com/badlogic/gdx/utils/arial-15.png"),
     71 			false, true);
     72 	}
     73 
     74 	/** Creates a BitmapFont using the default 15pt Arial font included in the libgdx JAR file. This is convenient to easily
     75 	 * display text without bothering without generating a bitmap font yourself.
     76 	 * @param flip If true, the glyphs will be flipped for use with a perspective where 0,0 is the upper left corner. */
     77 	public BitmapFont (boolean flip) {
     78 		this(Gdx.files.classpath("com/badlogic/gdx/utils/arial-15.fnt"), Gdx.files.classpath("com/badlogic/gdx/utils/arial-15.png"),
     79 			flip, true);
     80 	}
     81 
     82 	/** Creates a BitmapFont with the glyphs relative to the specified region. If the region is null, the glyph textures are loaded
     83 	 * from the image file given in the font file. The {@link #dispose()} method will not dispose the region's texture in this
     84 	 * case!
     85 	 * <p>
     86 	 * The font data is not flipped.
     87 	 * @param fontFile the font definition file
     88 	 * @param region The texture region containing the glyphs. The glyphs must be relative to the lower left corner (ie, the region
     89 	 *           should not be flipped). If the region is null the glyph images are loaded from the image path in the font file. */
     90 	public BitmapFont (FileHandle fontFile, TextureRegion region) {
     91 		this(fontFile, region, false);
     92 	}
     93 
     94 	/** Creates a BitmapFont with the glyphs relative to the specified region. If the region is null, the glyph textures are loaded
     95 	 * from the image file given in the font file. The {@link #dispose()} method will not dispose the region's texture in this
     96 	 * case!
     97 	 * @param region The texture region containing the glyphs. The glyphs must be relative to the lower left corner (ie, the region
     98 	 *           should not be flipped). If the region is null the glyph images are loaded from the image path in the font file.
     99 	 * @param flip If true, the glyphs will be flipped for use with a perspective where 0,0 is the upper left corner. */
    100 	public BitmapFont (FileHandle fontFile, TextureRegion region, boolean flip) {
    101 		this(new BitmapFontData(fontFile, flip), region, true);
    102 	}
    103 
    104 	/** Creates a BitmapFont from a BMFont file. The image file name is read from the BMFont file and the image is loaded from the
    105 	 * same directory. The font data is not flipped. */
    106 	public BitmapFont (FileHandle fontFile) {
    107 		this(fontFile, false);
    108 	}
    109 
    110 	/** Creates a BitmapFont from a BMFont file. The image file name is read from the BMFont file and the image is loaded from the
    111 	 * same directory.
    112 	 * @param flip If true, the glyphs will be flipped for use with a perspective where 0,0 is the upper left corner. */
    113 	public BitmapFont (FileHandle fontFile, boolean flip) {
    114 		this(new BitmapFontData(fontFile, flip), (TextureRegion)null, true);
    115 	}
    116 
    117 	/** Creates a BitmapFont from a BMFont file, using the specified image for glyphs. Any image specified in the BMFont file is
    118 	 * ignored.
    119 	 * @param flip If true, the glyphs will be flipped for use with a perspective where 0,0 is the upper left corner. */
    120 	public BitmapFont (FileHandle fontFile, FileHandle imageFile, boolean flip) {
    121 		this(fontFile, imageFile, flip, true);
    122 	}
    123 
    124 	/** Creates a BitmapFont from a BMFont file, using the specified image for glyphs. Any image specified in the BMFont file is
    125 	 * ignored.
    126 	 * @param flip If true, the glyphs will be flipped for use with a perspective where 0,0 is the upper left corner.
    127 	 * @param integer If true, rendering positions will be at integer values to avoid filtering artifacts. */
    128 	public BitmapFont (FileHandle fontFile, FileHandle imageFile, boolean flip, boolean integer) {
    129 		this(new BitmapFontData(fontFile, flip), new TextureRegion(new Texture(imageFile, false)), integer);
    130 		ownsTexture = true;
    131 	}
    132 
    133 	/** Constructs a new BitmapFont from the given {@link BitmapFontData} and {@link TextureRegion}. If the TextureRegion is null,
    134 	 * the image path(s) will be read from the BitmapFontData. The dispose() method will not dispose the texture of the region(s)
    135 	 * if the region is != null.
    136 	 * <p>
    137 	 * Passing a single TextureRegion assumes that your font only needs a single texture page. If you need to support multiple
    138 	 * pages, either let the Font read the images themselves (by specifying null as the TextureRegion), or by specifying each page
    139 	 * manually with the TextureRegion[] constructor.
    140 	 * @param integer If true, rendering positions will be at integer values to avoid filtering artifacts. */
    141 	public BitmapFont (BitmapFontData data, TextureRegion region, boolean integer) {
    142 		this(data, region != null ? Array.with(region) : null, integer);
    143 	}
    144 
    145 	/** Constructs a new BitmapFont from the given {@link BitmapFontData} and array of {@link TextureRegion}. If the TextureRegion
    146 	 * is null or empty, the image path(s) will be read from the BitmapFontData. The dispose() method will not dispose the texture
    147 	 * of the region(s) if the regions array is != null and not empty.
    148 	 * @param integer If true, rendering positions will be at integer values to avoid filtering artifacts. */
    149 	public BitmapFont (BitmapFontData data, Array<TextureRegion> pageRegions, boolean integer) {
    150 		this.flipped = data.flipped;
    151 		this.data = data;
    152 		this.integer = integer;
    153 
    154 		if (pageRegions == null || pageRegions.size == 0) {
    155 			// Load each path.
    156 			int n = data.imagePaths.length;
    157 			regions = new Array(n);
    158 			for (int i = 0; i < n; i++) {
    159 				FileHandle file;
    160 				if (data.fontFile == null)
    161 					file = Gdx.files.internal(data.imagePaths[i]);
    162 				else
    163 					file = Gdx.files.getFileHandle(data.imagePaths[i], data.fontFile.type());
    164 				regions.add(new TextureRegion(new Texture(file, false)));
    165 			}
    166 			ownsTexture = true;
    167 		} else {
    168 			regions = pageRegions;
    169 			ownsTexture = false;
    170 		}
    171 
    172 		cache = newFontCache();
    173 
    174 		load(data);
    175 	}
    176 
    177 	protected void load (BitmapFontData data) {
    178 		for (Glyph[] page : data.glyphs) {
    179 			if (page == null) continue;
    180 			for (Glyph glyph : page)
    181 				if (glyph != null) data.setGlyphRegion(glyph, regions.get(glyph.page));
    182 		}
    183 		if (data.missingGlyph != null) data.setGlyphRegion(data.missingGlyph, regions.get(data.missingGlyph.page));
    184 	}
    185 
    186 	/** Draws text at the specified position.
    187 	 * @see BitmapFontCache#addText(CharSequence, float, float) */
    188 	public GlyphLayout draw (Batch batch, CharSequence str, float x, float y) {
    189 		cache.clear();
    190 		GlyphLayout layout = cache.addText(str, x, y);
    191 		cache.draw(batch);
    192 		return layout;
    193 	}
    194 
    195 	/** Draws text at the specified position.
    196 	 * @see BitmapFontCache#addText(CharSequence, float, float, int, int, float, int, boolean, String) */
    197 	public GlyphLayout draw (Batch batch, CharSequence str, float x, float y, float targetWidth, int halign, boolean wrap) {
    198 		cache.clear();
    199 		GlyphLayout layout = cache.addText(str, x, y, targetWidth, halign, wrap);
    200 		cache.draw(batch);
    201 		return layout;
    202 	}
    203 
    204 	/** Draws text at the specified position.
    205 	 * @see BitmapFontCache#addText(CharSequence, float, float, int, int, float, int, boolean, String) */
    206 	public GlyphLayout draw (Batch batch, CharSequence str, float x, float y, int start, int end, float targetWidth, int halign,
    207 		boolean wrap) {
    208 		cache.clear();
    209 		GlyphLayout layout = cache.addText(str, x, y, start, end, targetWidth, halign, wrap);
    210 		cache.draw(batch);
    211 		return layout;
    212 	}
    213 
    214 	/** Draws text at the specified position.
    215 	 * @see BitmapFontCache#addText(CharSequence, float, float, int, int, float, int, boolean, String) */
    216 	public GlyphLayout draw (Batch batch, CharSequence str, float x, float y, int start, int end, float targetWidth, int halign,
    217 		boolean wrap, String truncate) {
    218 		cache.clear();
    219 		GlyphLayout layout = cache.addText(str, x, y, start, end, targetWidth, halign, wrap, truncate);
    220 		cache.draw(batch);
    221 		return layout;
    222 	}
    223 
    224 	/** Draws text at the specified position.
    225 	 * @see BitmapFontCache#addText(CharSequence, float, float, int, int, float, int, boolean, String) */
    226 	public void draw (Batch batch, GlyphLayout layout, float x, float y) {
    227 		cache.clear();
    228 		cache.addText(layout, x, y);
    229 		cache.draw(batch);
    230 	}
    231 
    232 	/** Returns the color of text drawn with this font. */
    233 	public Color getColor () {
    234 		return cache.getColor();
    235 	}
    236 
    237 	/** A convenience method for setting the font color. The color can also be set by modifying {@link #getColor()}. */
    238 	public void setColor (Color color) {
    239 		cache.getColor().set(color);
    240 	}
    241 
    242 	/** A convenience method for setting the font color. The color can also be set by modifying {@link #getColor()}. */
    243 	public void setColor (float r, float g, float b, float a) {
    244 		cache.getColor().set(r, g, b, a);
    245 	}
    246 
    247 	public float getScaleX () {
    248 		return data.scaleX;
    249 	}
    250 
    251 	public float getScaleY () {
    252 		return data.scaleY;
    253 	}
    254 
    255 	/** Returns the first texture region. This is included for backwards compatibility, and for convenience since most fonts only
    256 	 * use one texture page. For multi-page fonts, use {@link #getRegions()}.
    257 	 * @return the first texture region */
    258 	public TextureRegion getRegion () {
    259 		return regions.first();
    260 	}
    261 
    262 	/** Returns the array of TextureRegions that represents each texture page of glyphs.
    263 	 * @return the array of texture regions; modifying it may produce undesirable results */
    264 	public Array<TextureRegion> getRegions () {
    265 		return regions;
    266 	}
    267 
    268 	/** Returns the texture page at the given index.
    269 	 * @return the texture page at the given index */
    270 	public TextureRegion getRegion (int index) {
    271 		return regions.get(index);
    272 	}
    273 
    274 	/** Returns the line height, which is the distance from one line of text to the next. */
    275 	public float getLineHeight () {
    276 		return data.lineHeight;
    277 	}
    278 
    279 	/** Returns the width of the space character. */
    280 	public float getSpaceWidth () {
    281 		return data.spaceWidth;
    282 	}
    283 
    284 	/** Returns the x-height, which is the distance from the top of most lowercase characters to the baseline. */
    285 	public float getXHeight () {
    286 		return data.xHeight;
    287 	}
    288 
    289 	/** Returns the cap height, which is the distance from the top of most uppercase characters to the baseline. Since the drawing
    290 	 * position is the cap height of the first line, the cap height can be used to get the location of the baseline. */
    291 	public float getCapHeight () {
    292 		return data.capHeight;
    293 	}
    294 
    295 	/** Returns the ascent, which is the distance from the cap height to the top of the tallest glyph. */
    296 	public float getAscent () {
    297 		return data.ascent;
    298 	}
    299 
    300 	/** Returns the descent, which is the distance from the bottom of the glyph that extends the lowest to the baseline. This
    301 	 * number is negative. */
    302 	public float getDescent () {
    303 		return data.descent;
    304 	}
    305 
    306 	/** Returns true if this BitmapFont has been flipped for use with a y-down coordinate system. */
    307 	public boolean isFlipped () {
    308 		return flipped;
    309 	}
    310 
    311 	/** Disposes the texture used by this BitmapFont's region IF this BitmapFont created the texture. */
    312 	public void dispose () {
    313 		if (ownsTexture) {
    314 			for (int i = 0; i < regions.size; i++)
    315 				regions.get(i).getTexture().dispose();
    316 		}
    317 	}
    318 
    319 	/** Makes the specified glyphs fixed width. This can be useful to make the numbers in a font fixed width. Eg, when horizontally
    320 	 * centering a score or loading percentage text, it will not jump around as different numbers are shown. */
    321 	public void setFixedWidthGlyphs (CharSequence glyphs) {
    322 		BitmapFontData data = this.data;
    323 		int maxAdvance = 0;
    324 		for (int index = 0, end = glyphs.length(); index < end; index++) {
    325 			Glyph g = data.getGlyph(glyphs.charAt(index));
    326 			if (g != null && g.xadvance > maxAdvance) maxAdvance = g.xadvance;
    327 		}
    328 		for (int index = 0, end = glyphs.length(); index < end; index++) {
    329 			Glyph g = data.getGlyph(glyphs.charAt(index));
    330 			if (g == null) continue;
    331 			g.xoffset += Math.round((maxAdvance - g.xadvance) / 2);
    332 			g.xadvance = maxAdvance;
    333 			g.kerning = null;
    334 			g.fixedWidth = true;
    335 		}
    336 	}
    337 
    338 	/** Specifies whether to use integer positions. Default is to use them so filtering doesn't kick in as badly. */
    339 	public void setUseIntegerPositions (boolean integer) {
    340 		this.integer = integer;
    341 		cache.setUseIntegerPositions(integer);
    342 	}
    343 
    344 	/** Checks whether this font uses integer positions for drawing. */
    345 	public boolean usesIntegerPositions () {
    346 		return integer;
    347 	}
    348 
    349 	/** For expert usage -- returns the BitmapFontCache used by this font, for rendering to a sprite batch. This can be used, for
    350 	 * example, to manipulate glyph colors within a specific index.
    351 	 * @return the bitmap font cache used by this font */
    352 	public BitmapFontCache getCache () {
    353 		return cache;
    354 	}
    355 
    356 	/** Gets the underlying {@link BitmapFontData} for this BitmapFont. */
    357 	public BitmapFontData getData () {
    358 		return data;
    359 	}
    360 
    361 	/** @return whether the texture is owned by the font, font disposes the texture itself if true */
    362 	public boolean ownsTexture () {
    363 		return ownsTexture;
    364 	}
    365 
    366 	/** Sets whether the font owns the texture. In case it does, the font will also dispose of the texture when {@link #dispose()}
    367 	 * is called. Use with care!
    368 	 * @param ownsTexture whether the font owns the texture */
    369 	public void setOwnsTexture (boolean ownsTexture) {
    370 		this.ownsTexture = ownsTexture;
    371 	}
    372 
    373 	/** Creates a new BitmapFontCache for this font. Using this method allows the font to provide the BitmapFontCache
    374 	 * implementation to customize rendering.
    375 	 * <p>
    376 	 * Note this method is called by the BitmapFont constructors. If a subclass overrides this method, it will be called before the
    377 	 * subclass constructors. */
    378 	public BitmapFontCache newFontCache () {
    379 		return new BitmapFontCache(this, integer);
    380 	}
    381 
    382 	public String toString () {
    383 		if (data.fontFile != null) return data.fontFile.nameWithoutExtension();
    384 		return super.toString();
    385 	}
    386 
    387 	/** Represents a single character in a font page. */
    388 	public static class Glyph {
    389 		public int id;
    390 		public int srcX;
    391 		public int srcY;
    392 		public int width, height;
    393 		public float u, v, u2, v2;
    394 		public int xoffset, yoffset;
    395 		public int xadvance;
    396 		public byte[][] kerning;
    397 		public boolean fixedWidth;
    398 
    399 		/** The index to the texture page that holds this glyph. */
    400 		public int page = 0;
    401 
    402 		public int getKerning (char ch) {
    403 			if (kerning != null) {
    404 				byte[] page = kerning[ch >>> LOG2_PAGE_SIZE];
    405 				if (page != null) return page[ch & PAGE_SIZE - 1];
    406 			}
    407 			return 0;
    408 		}
    409 
    410 		public void setKerning (int ch, int value) {
    411 			if (kerning == null) kerning = new byte[PAGES][];
    412 			byte[] page = kerning[ch >>> LOG2_PAGE_SIZE];
    413 			if (page == null) kerning[ch >>> LOG2_PAGE_SIZE] = page = new byte[PAGE_SIZE];
    414 			page[ch & PAGE_SIZE - 1] = (byte)value;
    415 		}
    416 
    417 		public String toString () {
    418 			return Character.toString((char)id);
    419 		}
    420 	}
    421 
    422 	static int indexOf (CharSequence text, char ch, int start) {
    423 		final int n = text.length();
    424 		for (; start < n; start++)
    425 			if (text.charAt(start) == ch) return start;
    426 		return n;
    427 	}
    428 
    429 	/** Backing data for a {@link BitmapFont}. */
    430 	static public class BitmapFontData {
    431 		/** An array of the image paths, for multiple texture pages. */
    432 		public String[] imagePaths;
    433 		public FileHandle fontFile;
    434 		public boolean flipped;
    435 		public float padTop, padRight, padBottom, padLeft;
    436 		/** The distance from one line of text to the next. To set this value, use {@link #setLineHeight(float)}. */
    437 		public float lineHeight;
    438 		/** The distance from the top of most uppercase characters to the baseline. Since the drawing position is the cap height of
    439 		 * the first line, the cap height can be used to get the location of the baseline. */
    440 		public float capHeight = 1;
    441 		/** The distance from the cap height to the top of the tallest glyph. */
    442 		public float ascent;
    443 		/** The distance from the bottom of the glyph that extends the lowest to the baseline. This number is negative. */
    444 		public float descent;
    445 		public float down;
    446 		public float scaleX = 1, scaleY = 1;
    447 		public boolean markupEnabled;
    448 		/** The amount to add to the glyph X position when drawing a cursor between glyphs. This field is not set by the BMFont
    449 		 * file, it needs to be set manually depending on how the glyphs are rendered on the backing textures. */
    450 		public float cursorX;
    451 
    452 		public final Glyph[][] glyphs = new Glyph[PAGES][];
    453 		/** The glyph to display for characters not in the font. May be null. */
    454 		public Glyph missingGlyph;
    455 
    456 		/** The width of the space character. */
    457 		public float spaceWidth;
    458 		/** The x-height, which is the distance from the top of most lowercase characters to the baseline. */
    459 		public float xHeight = 1;
    460 
    461 		/** Additional characters besides whitespace where text is wrapped. Eg, a hypen (-). */
    462 		public char[] breakChars;
    463 		public char[] xChars = {'x', 'e', 'a', 'o', 'n', 's', 'r', 'c', 'u', 'm', 'v', 'w', 'z'};
    464 		public char[] capChars = {'M', 'N', 'B', 'D', 'C', 'E', 'F', 'K', 'A', 'G', 'H', 'I', 'J', 'L', 'O', 'P', 'Q', 'R', 'S',
    465 			'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
    466 
    467 		/** Creates an empty BitmapFontData for configuration before calling {@link #load(FileHandle, boolean)}, to subclass, or to
    468 		 * populate yourself, e.g. using stb-truetype or FreeType. */
    469 		public BitmapFontData () {
    470 		}
    471 
    472 		public BitmapFontData (FileHandle fontFile, boolean flip) {
    473 			this.fontFile = fontFile;
    474 			this.flipped = flip;
    475 			load(fontFile, flip);
    476 		}
    477 
    478 		public void load (FileHandle fontFile, boolean flip) {
    479 			if (imagePaths != null) throw new IllegalStateException("Already loaded.");
    480 
    481 			BufferedReader reader = new BufferedReader(new InputStreamReader(fontFile.read()), 512);
    482 			try {
    483 				String line = reader.readLine(); // info
    484 				if (line == null) throw new GdxRuntimeException("File is empty.");
    485 
    486 				line = line.substring(line.indexOf("padding=") + 8);
    487 				String[] padding = line.substring(0, line.indexOf(' ')).split(",", 4);
    488 				if (padding.length != 4) throw new GdxRuntimeException("Invalid padding.");
    489 				padTop = Integer.parseInt(padding[0]);
    490 				padLeft = Integer.parseInt(padding[1]);
    491 				padBottom = Integer.parseInt(padding[2]);
    492 				padRight = Integer.parseInt(padding[3]);
    493 				float padY = padTop + padBottom;
    494 
    495 				line = reader.readLine();
    496 				if (line == null) throw new GdxRuntimeException("Missing common header.");
    497 				String[] common = line.split(" ", 7); // At most we want the 6th element; i.e. "page=N"
    498 
    499 				// At least lineHeight and base are required.
    500 				if (common.length < 3) throw new GdxRuntimeException("Invalid common header.");
    501 
    502 				if (!common[1].startsWith("lineHeight=")) throw new GdxRuntimeException("Missing: lineHeight");
    503 				lineHeight = Integer.parseInt(common[1].substring(11));
    504 
    505 				if (!common[2].startsWith("base=")) throw new GdxRuntimeException("Missing: base");
    506 				float baseLine = Integer.parseInt(common[2].substring(5));
    507 
    508 				int pageCount = 1;
    509 				if (common.length >= 6 && common[5] != null && common[5].startsWith("pages=")) {
    510 					try {
    511 						pageCount = Math.max(1, Integer.parseInt(common[5].substring(6)));
    512 					} catch (NumberFormatException ignored) { // Use one page.
    513 					}
    514 				}
    515 
    516 				imagePaths = new String[pageCount];
    517 
    518 				// Read each page definition.
    519 				for (int p = 0; p < pageCount; p++) {
    520 					// Read each "page" info line.
    521 					line = reader.readLine();
    522 					if (line == null) throw new GdxRuntimeException("Missing additional page definitions.");
    523 					String[] pageLine = line.split(" ", 4);
    524 					if (!pageLine[2].startsWith("file=")) throw new GdxRuntimeException("Missing: file");
    525 
    526 					// Expect ID to mean "index".
    527 					if (pageLine[1].startsWith("id=")) {
    528 						try {
    529 							int pageID = Integer.parseInt(pageLine[1].substring(3));
    530 							if (pageID != p)
    531 								throw new GdxRuntimeException("Page IDs must be indices starting at 0: " + pageLine[1].substring(3));
    532 						} catch (NumberFormatException ex) {
    533 							throw new GdxRuntimeException("Invalid page id: " + pageLine[1].substring(3), ex);
    534 						}
    535 					}
    536 
    537 					String fileName = null;
    538 					if (pageLine[2].endsWith("\"")) {
    539 						fileName = pageLine[2].substring(6, pageLine[2].length() - 1);
    540 					} else {
    541 						fileName = pageLine[2].substring(5, pageLine[2].length());
    542 					}
    543 
    544 					imagePaths[p] = fontFile.parent().child(fileName).path().replaceAll("\\\\", "/");
    545 				}
    546 				descent = 0;
    547 
    548 				while (true) {
    549 					line = reader.readLine();
    550 					if (line == null) break; // EOF
    551 					if (line.startsWith("kernings ")) break; // Starting kernings block.
    552 					if (!line.startsWith("char ")) continue;
    553 
    554 					Glyph glyph = new Glyph();
    555 
    556 					StringTokenizer tokens = new StringTokenizer(line, " =");
    557 					tokens.nextToken();
    558 					tokens.nextToken();
    559 					int ch = Integer.parseInt(tokens.nextToken());
    560 					if (ch <= 0)
    561 						missingGlyph = glyph;
    562 					else if (ch <= Character.MAX_VALUE)
    563 						setGlyph(ch, glyph);
    564 					else
    565 						continue;
    566 					glyph.id = ch;
    567 					tokens.nextToken();
    568 					glyph.srcX = Integer.parseInt(tokens.nextToken());
    569 					tokens.nextToken();
    570 					glyph.srcY = Integer.parseInt(tokens.nextToken());
    571 					tokens.nextToken();
    572 					glyph.width = Integer.parseInt(tokens.nextToken());
    573 					tokens.nextToken();
    574 					glyph.height = Integer.parseInt(tokens.nextToken());
    575 					tokens.nextToken();
    576 					glyph.xoffset = Integer.parseInt(tokens.nextToken());
    577 					tokens.nextToken();
    578 					if (flip)
    579 						glyph.yoffset = Integer.parseInt(tokens.nextToken());
    580 					else
    581 						glyph.yoffset = -(glyph.height + Integer.parseInt(tokens.nextToken()));
    582 					tokens.nextToken();
    583 					glyph.xadvance = Integer.parseInt(tokens.nextToken());
    584 
    585 					// Check for page safely, it could be omitted or invalid.
    586 					if (tokens.hasMoreTokens()) tokens.nextToken();
    587 					if (tokens.hasMoreTokens()) {
    588 						try {
    589 							glyph.page = Integer.parseInt(tokens.nextToken());
    590 						} catch (NumberFormatException ignored) {
    591 						}
    592 					}
    593 
    594 					if (glyph.width > 0 && glyph.height > 0) descent = Math.min(baseLine + glyph.yoffset, descent);
    595 				}
    596 				descent += padBottom;
    597 
    598 				while (true) {
    599 					line = reader.readLine();
    600 					if (line == null) break;
    601 					if (!line.startsWith("kerning ")) break;
    602 
    603 					StringTokenizer tokens = new StringTokenizer(line, " =");
    604 					tokens.nextToken();
    605 					tokens.nextToken();
    606 					int first = Integer.parseInt(tokens.nextToken());
    607 					tokens.nextToken();
    608 					int second = Integer.parseInt(tokens.nextToken());
    609 					if (first < 0 || first > Character.MAX_VALUE || second < 0 || second > Character.MAX_VALUE) continue;
    610 					Glyph glyph = getGlyph((char)first);
    611 					tokens.nextToken();
    612 					int amount = Integer.parseInt(tokens.nextToken());
    613 					if (glyph != null) { // Kernings may exist for glyph pairs not contained in the font.
    614 						glyph.setKerning(second, amount);
    615 					}
    616 				}
    617 
    618 				Glyph spaceGlyph = getGlyph(' ');
    619 				if (spaceGlyph == null) {
    620 					spaceGlyph = new Glyph();
    621 					spaceGlyph.id = (int)' ';
    622 					Glyph xadvanceGlyph = getGlyph('l');
    623 					if (xadvanceGlyph == null) xadvanceGlyph = getFirstGlyph();
    624 					spaceGlyph.xadvance = xadvanceGlyph.xadvance;
    625 					setGlyph(' ', spaceGlyph);
    626 				}
    627 				if (spaceGlyph.width == 0) {
    628 					spaceGlyph.width = (int)(padLeft + spaceGlyph.xadvance + padRight);
    629 					spaceGlyph.xoffset = (int)-padLeft;
    630 				}
    631 				spaceWidth = spaceGlyph.width;
    632 
    633 				Glyph xGlyph = null;
    634 				for (char xChar : xChars) {
    635 					xGlyph = getGlyph(xChar);
    636 					if (xGlyph != null) break;
    637 				}
    638 				if (xGlyph == null) xGlyph = getFirstGlyph();
    639 				xHeight = xGlyph.height - padY;
    640 
    641 				Glyph capGlyph = null;
    642 				for (char capChar : capChars) {
    643 					capGlyph = getGlyph(capChar);
    644 					if (capGlyph != null) break;
    645 				}
    646 				if (capGlyph == null) {
    647 					for (Glyph[] page : this.glyphs) {
    648 						if (page == null) continue;
    649 						for (Glyph glyph : page) {
    650 							if (glyph == null || glyph.height == 0 || glyph.width == 0) continue;
    651 							capHeight = Math.max(capHeight, glyph.height);
    652 						}
    653 					}
    654 				} else
    655 					capHeight = capGlyph.height;
    656 				capHeight -= padY;
    657 
    658 				ascent = baseLine - capHeight;
    659 				down = -lineHeight;
    660 				if (flip) {
    661 					ascent = -ascent;
    662 					down = -down;
    663 				}
    664 			} catch (Exception ex) {
    665 				throw new GdxRuntimeException("Error loading font file: " + fontFile, ex);
    666 			} finally {
    667 				StreamUtils.closeQuietly(reader);
    668 			}
    669 		}
    670 
    671 		public void setGlyphRegion (Glyph glyph, TextureRegion region) {
    672 			Texture texture = region.getTexture();
    673 			float invTexWidth = 1.0f / texture.getWidth();
    674 			float invTexHeight = 1.0f / texture.getHeight();
    675 
    676 			float offsetX = 0, offsetY = 0;
    677 			float u = region.u;
    678 			float v = region.v;
    679 			float regionWidth = region.getRegionWidth();
    680 			float regionHeight = region.getRegionHeight();
    681 			if (region instanceof AtlasRegion) {
    682 				// Compensate for whitespace stripped from left and top edges.
    683 				AtlasRegion atlasRegion = (AtlasRegion)region;
    684 				offsetX = atlasRegion.offsetX;
    685 				offsetY = atlasRegion.originalHeight - atlasRegion.packedHeight - atlasRegion.offsetY;
    686 			}
    687 
    688 			float x = glyph.srcX;
    689 			float x2 = glyph.srcX + glyph.width;
    690 			float y = glyph.srcY;
    691 			float y2 = glyph.srcY + glyph.height;
    692 
    693 			// Shift glyph for left and top edge stripped whitespace. Clip glyph for right and bottom edge stripped whitespace.
    694 			if (offsetX > 0) {
    695 				x -= offsetX;
    696 				if (x < 0) {
    697 					glyph.width += x;
    698 					glyph.xoffset -= x;
    699 					x = 0;
    700 				}
    701 				x2 -= offsetX;
    702 				if (x2 > regionWidth) {
    703 					glyph.width -= x2 - regionWidth;
    704 					x2 = regionWidth;
    705 				}
    706 			}
    707 			if (offsetY > 0) {
    708 				y -= offsetY;
    709 				if (y < 0) {
    710 					glyph.height += y;
    711 					y = 0;
    712 				}
    713 				y2 -= offsetY;
    714 				if (y2 > regionHeight) {
    715 					float amount = y2 - regionHeight;
    716 					glyph.height -= amount;
    717 					glyph.yoffset += amount;
    718 					y2 = regionHeight;
    719 				}
    720 			}
    721 
    722 			glyph.u = u + x * invTexWidth;
    723 			glyph.u2 = u + x2 * invTexWidth;
    724 			if (flipped) {
    725 				glyph.v = v + y * invTexHeight;
    726 				glyph.v2 = v + y2 * invTexHeight;
    727 			} else {
    728 				glyph.v2 = v + y * invTexHeight;
    729 				glyph.v = v + y2 * invTexHeight;
    730 			}
    731 		}
    732 
    733 		/** Sets the line height, which is the distance from one line of text to the next. */
    734 		public void setLineHeight (float height) {
    735 			lineHeight = height * scaleY;
    736 			down = flipped ? lineHeight : -lineHeight;
    737 		}
    738 
    739 		public void setGlyph (int ch, Glyph glyph) {
    740 			Glyph[] page = glyphs[ch / PAGE_SIZE];
    741 			if (page == null) glyphs[ch / PAGE_SIZE] = page = new Glyph[PAGE_SIZE];
    742 			page[ch & PAGE_SIZE - 1] = glyph;
    743 		}
    744 
    745 		public Glyph getFirstGlyph () {
    746 			for (Glyph[] page : this.glyphs) {
    747 				if (page == null) continue;
    748 				for (Glyph glyph : page) {
    749 					if (glyph == null || glyph.height == 0 || glyph.width == 0) continue;
    750 					return glyph;
    751 				}
    752 			}
    753 			throw new GdxRuntimeException("No glyphs found.");
    754 		}
    755 
    756 		/** Returns true if the font has the glyph, or if the font has a {@link #missingGlyph}. */
    757 		public boolean hasGlyph (char ch) {
    758 			if (missingGlyph != null) return true;
    759 			return getGlyph(ch) != null;
    760 		}
    761 
    762 		/** Returns the glyph for the specified character, or null if no such glyph exists. Note that
    763 		 * {@link #getGlyphs(GlyphRun, CharSequence, int, int, boolean)} should be be used to shape a string of characters into a
    764 		 * list of glyphs. */
    765 		public Glyph getGlyph (char ch) {
    766 			Glyph[] page = glyphs[ch / PAGE_SIZE];
    767 			if (page != null) return page[ch & PAGE_SIZE - 1];
    768 			return null;
    769 		}
    770 
    771 		/** Using the specified string, populates the glyphs and positions of the specified glyph run.
    772 		 * @param str Characters to convert to glyphs. Will not contain newline or color tags. May contain "[[" for an escaped left
    773 		 *           square bracket.
    774 		 * @param tightBounds If true, the first {@link GlyphRun#xAdvances} entry is offset to prevent the first glyph from being
    775 		 *           drawn left of 0 and the last entry is offset to prevent the last glyph from being drawn right of the run
    776 		 *           width. */
    777 		public void getGlyphs (GlyphRun run, CharSequence str, int start, int end, boolean tightBounds) {
    778 			boolean markupEnabled = this.markupEnabled;
    779 			float scaleX = this.scaleX;
    780 			Glyph missingGlyph = this.missingGlyph;
    781 			Array<Glyph> glyphs = run.glyphs;
    782 			FloatArray xAdvances = run.xAdvances;
    783 
    784 			// Guess at number of glyphs needed.
    785 			glyphs.ensureCapacity(end - start);
    786 			xAdvances.ensureCapacity(end - start + 1);
    787 
    788 			Glyph lastGlyph = null;
    789 			while (start < end) {
    790 				char ch = str.charAt(start++);
    791 				Glyph glyph = getGlyph(ch);
    792 				if (glyph == null) {
    793 					if (missingGlyph == null) continue;
    794 					glyph = missingGlyph;
    795 				}
    796 
    797 				glyphs.add(glyph);
    798 
    799 				if (lastGlyph == null) // First glyph.
    800 					xAdvances.add((!tightBounds || glyph.fixedWidth) ? 0 : -glyph.xoffset * scaleX - padLeft);
    801 				else
    802 					xAdvances.add((lastGlyph.xadvance + lastGlyph.getKerning(ch)) * scaleX);
    803 				lastGlyph = glyph;
    804 
    805 				// "[[" is an escaped left square bracket, skip second character.
    806 				if (markupEnabled && ch == '[' && start < end && str.charAt(start) == '[') start++;
    807 			}
    808 			if (lastGlyph != null) {
    809 				float lastGlyphWidth = (!tightBounds || lastGlyph.fixedWidth) ? lastGlyph.xadvance
    810 					: lastGlyph.xoffset + lastGlyph.width - padRight;
    811 				xAdvances.add(lastGlyphWidth * scaleX);
    812 			}
    813 		}
    814 
    815 		/** Returns the first valid glyph index to use to wrap to the next line, starting at the specified start index and
    816 		 * (typically) moving toward the beginning of the glyphs array. */
    817 		public int getWrapIndex (Array<Glyph> glyphs, int start) {
    818 			int i = start - 1;
    819 			for (; i >= 1; i--)
    820 				if (!isWhitespace((char)glyphs.get(i).id)) break;
    821 			for (; i >= 1; i--) {
    822 				char ch = (char)glyphs.get(i).id;
    823 				if (isWhitespace(ch) || isBreakChar(ch)) return i + 1;
    824 			}
    825 			return 0;
    826 		}
    827 
    828 		public boolean isBreakChar (char c) {
    829 			if (breakChars == null) return false;
    830 			for (char br : breakChars)
    831 				if (c == br) return true;
    832 			return false;
    833 		}
    834 
    835 		public boolean isWhitespace (char c) {
    836 			switch (c) {
    837 			case '\n':
    838 			case '\r':
    839 			case '\t':
    840 			case ' ':
    841 				return true;
    842 			default:
    843 				return false;
    844 			}
    845 		}
    846 
    847 		/** Returns the image path for the texture page at the given index (the "id" in the BMFont file). */
    848 		public String getImagePath (int index) {
    849 			return imagePaths[index];
    850 		}
    851 
    852 		public String[] getImagePaths () {
    853 			return imagePaths;
    854 		}
    855 
    856 		public FileHandle getFontFile () {
    857 			return fontFile;
    858 		}
    859 
    860 		/** Scales the font by the specified amounts on both axes
    861 		 * <p>
    862 		 * Note that smoother scaling can be achieved if the texture backing the BitmapFont is using {@link TextureFilter#Linear}.
    863 		 * The default is Nearest, so use a BitmapFont constructor that takes a {@link TextureRegion}.
    864 		 * @throws IllegalArgumentException if scaleX or scaleY is zero. */
    865 		public void setScale (float scaleX, float scaleY) {
    866 			if (scaleX == 0) throw new IllegalArgumentException("scaleX cannot be 0.");
    867 			if (scaleY == 0) throw new IllegalArgumentException("scaleY cannot be 0.");
    868 			float x = scaleX / this.scaleX;
    869 			float y = scaleY / this.scaleY;
    870 			lineHeight *= y;
    871 			spaceWidth *= x;
    872 			xHeight *= y;
    873 			capHeight *= y;
    874 			ascent *= y;
    875 			descent *= y;
    876 			down *= y;
    877 			padTop *= y;
    878 			padLeft *= y;
    879 			padBottom *= y;
    880 			padRight *= y;
    881 			this.scaleX = scaleX;
    882 			this.scaleY = scaleY;
    883 		}
    884 
    885 		/** Scales the font by the specified amount in both directions.
    886 		 * @see #setScale(float, float)
    887 		 * @throws IllegalArgumentException if scaleX or scaleY is zero. */
    888 		public void setScale (float scaleXY) {
    889 			setScale(scaleXY, scaleXY);
    890 		}
    891 
    892 		/** Sets the font's scale relative to the current scale.
    893 		 * @see #setScale(float, float)
    894 		 * @throws IllegalArgumentException if the resulting scale is zero. */
    895 		public void scale (float amount) {
    896 			setScale(scaleX + amount, scaleY + amount);
    897 		}
    898 	}
    899 }
    900