• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.graphics.g2d.freetype;
18 
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.nio.ByteBuffer;
22 
23 import com.badlogic.gdx.Gdx;
24 import com.badlogic.gdx.files.FileHandle;
25 import com.badlogic.gdx.graphics.Color;
26 import com.badlogic.gdx.graphics.Pixmap;
27 import com.badlogic.gdx.graphics.Pixmap.Format;
28 import com.badlogic.gdx.graphics.Texture.TextureFilter;
29 import com.badlogic.gdx.graphics.g2d.BitmapFont;
30 import com.badlogic.gdx.graphics.g2d.BitmapFont.BitmapFontData;
31 import com.badlogic.gdx.graphics.g2d.BitmapFont.Glyph;
32 import com.badlogic.gdx.graphics.g2d.GlyphLayout.GlyphRun;
33 import com.badlogic.gdx.graphics.g2d.PixmapPacker;
34 import com.badlogic.gdx.graphics.g2d.PixmapPacker.GuillotineStrategy;
35 import com.badlogic.gdx.graphics.g2d.PixmapPacker.PackStrategy;
36 import com.badlogic.gdx.graphics.g2d.PixmapPacker.SkylineStrategy;
37 import com.badlogic.gdx.graphics.g2d.TextureRegion;
38 import com.badlogic.gdx.graphics.g2d.freetype.FreeType.Bitmap;
39 import com.badlogic.gdx.graphics.g2d.freetype.FreeType.Face;
40 import com.badlogic.gdx.graphics.g2d.freetype.FreeType.GlyphMetrics;
41 import com.badlogic.gdx.graphics.g2d.freetype.FreeType.GlyphSlot;
42 import com.badlogic.gdx.graphics.g2d.freetype.FreeType.Library;
43 import com.badlogic.gdx.graphics.g2d.freetype.FreeType.SizeMetrics;
44 import com.badlogic.gdx.graphics.g2d.freetype.FreeType.Stroker;
45 import com.badlogic.gdx.math.MathUtils;
46 import com.badlogic.gdx.math.Rectangle;
47 import com.badlogic.gdx.utils.Array;
48 import com.badlogic.gdx.utils.BufferUtils;
49 import com.badlogic.gdx.utils.Disposable;
50 import com.badlogic.gdx.utils.GdxRuntimeException;
51 import com.badlogic.gdx.utils.StreamUtils;
52 
53 /** Generates {@link BitmapFont} and {@link BitmapFontData} instances from TrueType, OTF, and other FreeType supported fonts.
54  * </p>
55  *
56  * Usage example:
57  *
58  * <pre>
59  * FreeTypeFontGenerator gen = new FreeTypeFontGenerator(Gdx.files.internal(&quot;myfont.ttf&quot;));
60  * BitmapFont font = gen.generateFont(16);
61  * gen.dispose();
62  * </pre>
63  *
64  * The generator has to be disposed once it is no longer used. The returned {@link BitmapFont} instances are managed by the user
65  * and have to be disposed as usual.
66  *
67  * @author mzechner
68  * @author Nathan Sweet
69  * @author Rob Rendell */
70 public class FreeTypeFontGenerator implements Disposable {
71 	static public final String DEFAULT_CHARS = "\u0000ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890\"!`?'.,;:()[]{}<>|/@\\^$€-%+=#_&~*\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008A\u008B\u008C\u008D\u008E\u008F\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009A\u009B\u009C\u009D\u009E\u009F\u00A0\u00A1\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7\u00A8\u00A9\u00AA\u00AB\u00AC\u00AD\u00AE\u00AF\u00B0\u00B1\u00B2\u00B3\u00B4\u00B5\u00B6\u00B7\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF";
72 
73 	/** A hint to scale the texture as needed, without capping it at any maximum size */
74 	static public final int NO_MAXIMUM = -1;
75 
76 	/** The maximum texture size allowed by generateData, when storing in a texture atlas. Multiple texture pages will be created
77 	 * if necessary. Default is 1024.
78 	 * @see #setMaxTextureSize(int) */
79 	static private int maxTextureSize = 1024;
80 
81 	final Library library;
82 	final Face face;
83 	final String name;
84 	boolean bitmapped = false;
85 	private int pixelWidth, pixelHeight;
86 
87 	/** Creates a new generator from the given font file. Uses {@link FileHandle#length()} to determine the file size. If the file
88 	 * length could not be determined (it was 0), an extra copy of the font bytes is performed. Throws a
89 	 * {@link GdxRuntimeException} if loading did not succeed. */
FreeTypeFontGenerator(FileHandle fontFile)90 	public FreeTypeFontGenerator (FileHandle fontFile) {
91 		name = fontFile.pathWithoutExtension();
92 		int fileSize = (int)fontFile.length();
93 
94 		library = FreeType.initFreeType();
95 		if (library == null) throw new GdxRuntimeException("Couldn't initialize FreeType");
96 
97 		ByteBuffer buffer;
98 		InputStream input = fontFile.read();
99 		try {
100 			if (fileSize == 0) {
101 				// Copy to a byte[] to get the file size, then copy to the buffer.
102 				byte[] data = StreamUtils.copyStreamToByteArray(input, fileSize > 0 ? (int)(fileSize * 1.5f) : 1024 * 16);
103 				buffer = BufferUtils.newUnsafeByteBuffer(data.length);
104 				BufferUtils.copy(data, 0, buffer, data.length);
105 			} else {
106 				// Trust the specified file size.
107 				buffer = BufferUtils.newUnsafeByteBuffer(fileSize);
108 				StreamUtils.copyStream(input, buffer);
109 			}
110 		} catch (IOException ex) {
111 			throw new GdxRuntimeException(ex);
112 		} finally {
113 			StreamUtils.closeQuietly(input);
114 		}
115 
116 		face = library.newMemoryFace(buffer, 0);
117 		if (face == null) throw new GdxRuntimeException("Couldn't create face for font: " + fontFile);
118 
119 		if (checkForBitmapFont()) return;
120 		setPixelSizes(0, 15);
121 	}
122 
getLoadingFlags(FreeTypeFontParameter parameter)123 	private int getLoadingFlags (FreeTypeFontParameter parameter) {
124 		int loadingFlags = FreeType.FT_LOAD_DEFAULT;
125 		switch (parameter.hinting) {
126 		case None:
127 			loadingFlags |= FreeType.FT_LOAD_NO_HINTING;
128 			break;
129 		case Slight:
130 			loadingFlags |= FreeType.FT_LOAD_FORCE_AUTOHINT | FreeType.FT_LOAD_TARGET_LIGHT;
131 			break;
132 		case Medium:
133 			loadingFlags |= FreeType.FT_LOAD_FORCE_AUTOHINT | FreeType.FT_LOAD_TARGET_NORMAL;
134 			break;
135 		case Full:
136 			loadingFlags |= FreeType.FT_LOAD_FORCE_AUTOHINT | FreeType.FT_LOAD_TARGET_MONO;
137 			break;
138 		}
139 		return loadingFlags;
140 	}
141 
loadChar(int c)142 	private boolean loadChar (int c) {
143 		return loadChar(c, FreeType.FT_LOAD_DEFAULT | FreeType.FT_LOAD_FORCE_AUTOHINT);
144 	}
145 
loadChar(int c, int flags)146 	private boolean loadChar (int c, int flags) {
147 		return face.loadChar(c, flags);
148 	}
149 
checkForBitmapFont()150 	private boolean checkForBitmapFont () {
151 		int faceFlags = face.getFaceFlags();
152 		if (((faceFlags & FreeType.FT_FACE_FLAG_FIXED_SIZES) == FreeType.FT_FACE_FLAG_FIXED_SIZES)
153 			&& ((faceFlags & FreeType.FT_FACE_FLAG_HORIZONTAL) == FreeType.FT_FACE_FLAG_HORIZONTAL)) {
154 			if (loadChar(32)) {
155 				GlyphSlot slot = face.getGlyph();
156 				if (slot.getFormat() == 1651078259) {
157 					bitmapped = true;
158 				}
159 			}
160 		}
161 		return bitmapped;
162 	}
163 
generateFont(FreeTypeFontParameter parameter)164 	public BitmapFont generateFont (FreeTypeFontParameter parameter) {
165 		return generateFont(parameter, new FreeTypeBitmapFontData());
166 	}
167 
168 	/** Generates a new {@link BitmapFont}. The size is expressed in pixels. Throws a GdxRuntimeException if the font could not be
169 	 * generated. Using big sizes might cause such an exception.
170 	 * @param parameter configures how the font is generated */
generateFont(FreeTypeFontParameter parameter, FreeTypeBitmapFontData data)171 	public BitmapFont generateFont (FreeTypeFontParameter parameter, FreeTypeBitmapFontData data) {
172 		generateData(parameter, data);
173 		if (data.regions == null && parameter.packer != null) {
174 			data.regions = new Array();
175 			parameter.packer.updateTextureRegions(data.regions, parameter.minFilter, parameter.magFilter, parameter.genMipMaps);
176 		}
177 		BitmapFont font = new BitmapFont(data, data.regions, true);
178 		font.setOwnsTexture(parameter.packer == null);
179 		return font;
180 	}
181 
182 	/** Uses ascender and descender of font to calculate real height that makes all glyphs to fit in given pixel size. Source:
183 	 * http://nothings.org/stb/stb_truetype.h / stbtt_ScaleForPixelHeight */
scaleForPixelHeight(int height)184 	public int scaleForPixelHeight (int height) {
185 		setPixelSizes(0, height);
186 		SizeMetrics fontMetrics = face.getSize().getMetrics();
187 		int ascent = FreeType.toInt(fontMetrics.getAscender());
188 		int descent = FreeType.toInt(fontMetrics.getDescender());
189 		return height * height / (ascent - descent);
190 	}
191 
192 	/** Uses max advance, ascender and descender of font to calculate real height that makes any n glyphs to fit in given pixel
193 	 * width.
194 	 * @param width the max width to fit (in pixels)
195 	 * @param numChars max number of characters that to fill width */
scaleForPixelWidth(int width, int numChars)196 	public int scaleForPixelWidth (int width, int numChars) {
197 		SizeMetrics fontMetrics = face.getSize().getMetrics();
198 		int advance = FreeType.toInt(fontMetrics.getMaxAdvance());
199 		int ascent = FreeType.toInt(fontMetrics.getAscender());
200 		int descent = FreeType.toInt(fontMetrics.getDescender());
201 		int unscaledHeight = ascent - descent;
202 		int height = unscaledHeight * width / (advance * numChars);
203 		setPixelSizes(0, height);
204 		return height;
205 	}
206 
207 	/** Uses max advance, ascender and descender of font to calculate real height that makes any n glyphs to fit in given pixel
208 	 * width and height.
209 	 * @param width the max width to fit (in pixels)
210 	 * @param height the max height to fit (in pixels)
211 	 * @param numChars max number of characters that to fill width */
scaleToFitSquare(int width, int height, int numChars)212 	public int scaleToFitSquare (int width, int height, int numChars) {
213 		return Math.min(scaleForPixelHeight(height), scaleForPixelWidth(width, numChars));
214 	}
215 
216 	public class GlyphAndBitmap {
217 		public Glyph glyph;
218 		public Bitmap bitmap;
219 	}
220 
221 	/** Returns null if glyph was not found. If there is nothing to render, for example with various space characters, then bitmap
222 	 * is null. */
generateGlyphAndBitmap(int c, int size, boolean flip)223 	public GlyphAndBitmap generateGlyphAndBitmap (int c, int size, boolean flip) {
224 		setPixelSizes(0, size);
225 
226 		SizeMetrics fontMetrics = face.getSize().getMetrics();
227 		int baseline = FreeType.toInt(fontMetrics.getAscender());
228 
229 		// Check if character exists in this font.
230 		// 0 means 'undefined character code'
231 		if (face.getCharIndex(c) == 0) {
232 			return null;
233 		}
234 
235 		// Try to load character
236 		if (!loadChar(c)) {
237 			throw new GdxRuntimeException("Unable to load character!");
238 		}
239 
240 		GlyphSlot slot = face.getGlyph();
241 
242 		// Try to render to bitmap
243 		Bitmap bitmap;
244 		if (bitmapped) {
245 			bitmap = slot.getBitmap();
246 		} else if (!slot.renderGlyph(FreeType.FT_RENDER_MODE_NORMAL)) {
247 			bitmap = null;
248 		} else {
249 			bitmap = slot.getBitmap();
250 		}
251 
252 		GlyphMetrics metrics = slot.getMetrics();
253 
254 		Glyph glyph = new Glyph();
255 		if (bitmap != null) {
256 			glyph.width = bitmap.getWidth();
257 			glyph.height = bitmap.getRows();
258 		} else {
259 			glyph.width = 0;
260 			glyph.height = 0;
261 		}
262 		glyph.xoffset = slot.getBitmapLeft();
263 		glyph.yoffset = flip ? -slot.getBitmapTop() + baseline : -(glyph.height - slot.getBitmapTop()) - baseline;
264 		glyph.xadvance = FreeType.toInt(metrics.getHoriAdvance());
265 		glyph.srcX = 0;
266 		glyph.srcY = 0;
267 		glyph.id = c;
268 
269 		GlyphAndBitmap result = new GlyphAndBitmap();
270 		result.glyph = glyph;
271 		result.bitmap = bitmap;
272 		return result;
273 	}
274 
275 	/** Generates a new {@link BitmapFontData} instance, expert usage only. Throws a GdxRuntimeException if something went wrong.
276 	 * @param size the size in pixels */
generateData(int size)277 	public FreeTypeBitmapFontData generateData (int size) {
278 		FreeTypeFontParameter parameter = new FreeTypeFontParameter();
279 		parameter.size = size;
280 		return generateData(parameter);
281 	}
282 
generateData(FreeTypeFontParameter parameter)283 	public FreeTypeBitmapFontData generateData (FreeTypeFontParameter parameter) {
284 		return generateData(parameter, new FreeTypeBitmapFontData());
285 	}
286 
setPixelSizes(int pixelWidth, int pixelHeight)287 	void setPixelSizes (int pixelWidth, int pixelHeight) {
288 		this.pixelWidth = pixelWidth;
289 		this.pixelHeight = pixelHeight;
290 		if (!bitmapped && !face.setPixelSizes(pixelWidth, pixelHeight)) throw new GdxRuntimeException("Couldn't set size for font");
291 	}
292 
293 	/** Generates a new {@link BitmapFontData} instance, expert usage only. Throws a GdxRuntimeException if something went wrong.
294 	 * @param parameter configures how the font is generated */
generateData(FreeTypeFontParameter parameter, FreeTypeBitmapFontData data)295 	public FreeTypeBitmapFontData generateData (FreeTypeFontParameter parameter, FreeTypeBitmapFontData data) {
296 		parameter = parameter == null ? new FreeTypeFontParameter() : parameter;
297 		char[] characters = parameter.characters.toCharArray();
298 		int charactersLength = characters.length;
299 		boolean incremental = parameter.incremental;
300 		int flags = getLoadingFlags(parameter);
301 
302 		setPixelSizes(0, parameter.size);
303 
304 		// set general font data
305 		SizeMetrics fontMetrics = face.getSize().getMetrics();
306 		data.flipped = parameter.flip;
307 		data.ascent = FreeType.toInt(fontMetrics.getAscender());
308 		data.descent = FreeType.toInt(fontMetrics.getDescender());
309 		data.lineHeight = FreeType.toInt(fontMetrics.getHeight());
310 		float baseLine = data.ascent;
311 
312 		// if bitmapped
313 		if (bitmapped && (data.lineHeight == 0)) {
314 			for (int c = 32; c < (32 + face.getNumGlyphs()); c++) {
315 				if (loadChar(c, flags)) {
316 					int lh = FreeType.toInt(face.getGlyph().getMetrics().getHeight());
317 					data.lineHeight = (lh > data.lineHeight) ? lh : data.lineHeight;
318 				}
319 			}
320 		}
321 		data.lineHeight += parameter.spaceY;
322 
323 		// determine space width
324 		if (loadChar(' ', flags) || loadChar('l', flags)) {
325 			data.spaceWidth = FreeType.toInt(face.getGlyph().getMetrics().getHoriAdvance());
326 		} else {
327 			data.spaceWidth = face.getMaxAdvanceWidth(); // Possibly very wrong.
328 		}
329 
330 		// determine x-height
331 		for (char xChar : data.xChars) {
332 			if (!loadChar(xChar, flags)) continue;
333 			data.xHeight = FreeType.toInt(face.getGlyph().getMetrics().getHeight());
334 			break;
335 		}
336 		if (data.xHeight == 0) throw new GdxRuntimeException("No x-height character found in font");
337 
338 		// determine cap height
339 		for (char capChar : data.capChars) {
340 			if (!loadChar(capChar, flags)) continue;
341 			data.capHeight = FreeType.toInt(face.getGlyph().getMetrics().getHeight());
342 			break;
343 		}
344 		if (!bitmapped && data.capHeight == 1) throw new GdxRuntimeException("No cap character found in font");
345 
346 		data.ascent -= data.capHeight;
347 		data.down = -data.lineHeight;
348 		if (parameter.flip) {
349 			data.ascent = -data.ascent;
350 			data.down = -data.down;
351 		}
352 
353 		boolean ownsAtlas = false;
354 
355 		PixmapPacker packer = parameter.packer;
356 
357 		if (packer == null) {
358 			// Create a packer.
359 			int size;
360 			PackStrategy packStrategy;
361 			if (incremental) {
362 				size = maxTextureSize;
363 				packStrategy = new GuillotineStrategy();
364 			} else {
365 				int maxGlyphHeight = (int)Math.ceil(data.lineHeight);
366 				size = MathUtils.nextPowerOfTwo((int)Math.sqrt(maxGlyphHeight * maxGlyphHeight * charactersLength));
367 				if (maxTextureSize > 0) size = Math.min(size, maxTextureSize);
368 				packStrategy = new SkylineStrategy();
369 			}
370 			ownsAtlas = true;
371 			packer = new PixmapPacker(size, size, Format.RGBA8888, 1, false, packStrategy);
372 			packer.setTransparentColor(parameter.color);
373 			packer.getTransparentColor().a = 0;
374 			if (parameter.borderWidth > 0) {
375 				packer.setTransparentColor(parameter.borderColor);
376 				packer.getTransparentColor().a = 0;
377 			}
378 		}
379 
380 		if (incremental) data.glyphs = new Array(charactersLength + 32);
381 
382 		Stroker stroker = null;
383 		if (parameter.borderWidth > 0) {
384 			stroker = library.createStroker();
385 			stroker.set((int)(parameter.borderWidth * 64f),
386 				parameter.borderStraight ? FreeType.FT_STROKER_LINECAP_BUTT : FreeType.FT_STROKER_LINECAP_ROUND,
387 				parameter.borderStraight ? FreeType.FT_STROKER_LINEJOIN_MITER_FIXED : FreeType.FT_STROKER_LINEJOIN_ROUND, 0);
388 		}
389 
390 		Glyph missingGlyph = createGlyph('\0', data, parameter, stroker, baseLine, packer);
391 		if (missingGlyph != null && missingGlyph.width != 0 && missingGlyph.height != 0) {
392 			data.setGlyph('\0', missingGlyph);
393 			if (incremental) data.glyphs.add(missingGlyph);
394 		}
395 
396 		// Create glyphs largest height first for best packing.
397 		int[] heights = new int[charactersLength];
398 		for (int i = 0, n = charactersLength; i < n; i++) {
399 			int height = loadChar(characters[i], flags) ? FreeType.toInt(face.getGlyph().getMetrics().getHeight()) : 0;
400 			heights[i] = height;
401 		}
402 		int heightsCount = heights.length;
403 		while (heightsCount > 0) {
404 			int best = 0, maxHeight = heights[0];
405 			for (int i = 1; i < heightsCount; i++) {
406 				int height = heights[i];
407 				if (height > maxHeight) {
408 					maxHeight = height;
409 					best = i;
410 				}
411 			}
412 
413 			char c = characters[best];
414 			Glyph glyph = createGlyph(c, data, parameter, stroker, baseLine, packer);
415 			if (glyph != null) {
416 				data.setGlyph(c, glyph);
417 				if (incremental) data.glyphs.add(glyph);
418 			}
419 
420 			heightsCount--;
421 			heights[best] = heights[heightsCount];
422 			char tmpChar = characters[best];
423 			characters[best] = characters[heightsCount];
424 			characters[heightsCount] = tmpChar;
425 		}
426 
427 		if (stroker != null && !incremental) stroker.dispose();
428 
429 		if (incremental) {
430 			data.generator = this;
431 			data.parameter = parameter;
432 			data.stroker = stroker;
433 			data.packer = packer;
434 		}
435 
436 		// Generate kerning.
437 		parameter.kerning &= face.hasKerning();
438 		if (parameter.kerning) {
439 			for (int i = 0; i < charactersLength; i++) {
440 				char firstChar = characters[i];
441 				Glyph first = data.getGlyph(firstChar);
442 				if (first == null) continue;
443 				int firstIndex = face.getCharIndex(firstChar);
444 				for (int ii = i; ii < charactersLength; ii++) {
445 					char secondChar = characters[ii];
446 					Glyph second = data.getGlyph(secondChar);
447 					if (second == null) continue;
448 					int secondIndex = face.getCharIndex(secondChar);
449 
450 					int kerning = face.getKerning(firstIndex, secondIndex, 0);
451 					if (kerning != 0) first.setKerning(secondChar, FreeType.toInt(kerning));
452 
453 					kerning = face.getKerning(secondIndex, firstIndex, 0);
454 					if (kerning != 0) second.setKerning(firstChar, FreeType.toInt(kerning));
455 				}
456 			}
457 		}
458 
459 		// Generate texture regions.
460 		if (ownsAtlas) {
461 			data.regions = new Array();
462 			packer.updateTextureRegions(data.regions, parameter.minFilter, parameter.magFilter, parameter.genMipMaps);
463 		}
464 
465 		// Set space glyph.
466 		Glyph spaceGlyph = data.getGlyph(' ');
467 		if (spaceGlyph == null) {
468 			spaceGlyph = new Glyph();
469 			spaceGlyph.xadvance = (int)data.spaceWidth + parameter.spaceX;
470 			spaceGlyph.id = (int)' ';
471 			data.setGlyph(' ', spaceGlyph);
472 		}
473 		if (spaceGlyph.width == 0) spaceGlyph.width = (int)(spaceGlyph.xadvance + data.padRight);
474 
475 		return data;
476 	}
477 
478 	/** @return null if glyph was not found. */
createGlyph(char c, FreeTypeBitmapFontData data, FreeTypeFontParameter parameter, Stroker stroker, float baseLine, PixmapPacker packer)479 	Glyph createGlyph (char c, FreeTypeBitmapFontData data, FreeTypeFontParameter parameter, Stroker stroker, float baseLine,
480 		PixmapPacker packer) {
481 
482 		boolean missing = face.getCharIndex(c) == 0 && c != 0;
483 		if (missing) return null;
484 
485 		if (!loadChar(c, getLoadingFlags(parameter))) return null;
486 
487 		GlyphSlot slot = face.getGlyph();
488 		FreeType.Glyph mainGlyph = slot.getGlyph();
489 		try {
490 			mainGlyph.toBitmap(parameter.mono ? FreeType.FT_RENDER_MODE_MONO : FreeType.FT_RENDER_MODE_NORMAL);
491 		} catch (GdxRuntimeException e) {
492 			mainGlyph.dispose();
493 			Gdx.app.log("FreeTypeFontGenerator", "Couldn't render char: " + c);
494 			return null;
495 		}
496 		Bitmap mainBitmap = mainGlyph.getBitmap();
497 		Pixmap mainPixmap = mainBitmap.getPixmap(Format.RGBA8888, parameter.color, parameter.gamma);
498 
499 		if (mainBitmap.getWidth() != 0 && mainBitmap.getRows() != 0) {
500 			int offsetX = 0, offsetY = 0;
501 			if (parameter.borderWidth > 0) {
502 				// execute stroker; this generates a glyph "extended" along the outline
503 				int top = mainGlyph.getTop(), left = mainGlyph.getLeft();
504 				FreeType.Glyph borderGlyph = slot.getGlyph();
505 				borderGlyph.strokeBorder(stroker, false);
506 				borderGlyph.toBitmap(parameter.mono ? FreeType.FT_RENDER_MODE_MONO : FreeType.FT_RENDER_MODE_NORMAL);
507 				offsetX = left - borderGlyph.getLeft();
508 				offsetY = -(top - borderGlyph.getTop());
509 
510 				// Render border (pixmap is bigger than main).
511 				Bitmap borderBitmap = borderGlyph.getBitmap();
512 				Pixmap borderPixmap = borderBitmap.getPixmap(Format.RGBA8888, parameter.borderColor, parameter.borderGamma);
513 
514 				// Draw main glyph on top of border.
515 				for (int i = 0, n = parameter.renderCount; i < n; i++)
516 					borderPixmap.drawPixmap(mainPixmap, offsetX, offsetY);
517 
518 				mainPixmap.dispose();
519 				mainGlyph.dispose();
520 				mainPixmap = borderPixmap;
521 				mainGlyph = borderGlyph;
522 			}
523 
524 			if (parameter.shadowOffsetX != 0 || parameter.shadowOffsetY != 0) {
525 				int mainW = mainPixmap.getWidth(), mainH = mainPixmap.getHeight();
526 				int shadowOffsetX = Math.max(parameter.shadowOffsetX, 0), shadowOffsetY = Math.max(parameter.shadowOffsetY, 0);
527 				int shadowW = mainW + Math.abs(parameter.shadowOffsetX), shadowH = mainH + Math.abs(parameter.shadowOffsetY);
528 				Pixmap shadowPixmap = new Pixmap(shadowW, shadowH, mainPixmap.getFormat());
529 
530 				Color shadowColor = parameter.shadowColor;
531 				byte r = (byte)(shadowColor.r * 255), g = (byte)(shadowColor.g * 255), b = (byte)(shadowColor.b * 255);
532 				float a = shadowColor.a;
533 
534 				ByteBuffer mainPixels = mainPixmap.getPixels();
535 				ByteBuffer shadowPixels = shadowPixmap.getPixels();
536 				for (int y = 0; y < mainH; y++) {
537 					int shadowRow = shadowW * (y + shadowOffsetY) + shadowOffsetX;
538 					for (int x = 0; x < mainW; x++) {
539 						int mainPixel = (mainW * y + x) * 4;
540 						byte mainA = mainPixels.get(mainPixel + 3);
541 						if (mainA == 0) continue;
542 						int shadowPixel = (shadowRow + x) * 4;
543 						shadowPixels.put(shadowPixel, r);
544 						shadowPixels.put(shadowPixel + 1, g);
545 						shadowPixels.put(shadowPixel + 2, b);
546 						shadowPixels.put(shadowPixel + 3, (byte)((mainA & 0xff) * a));
547 					}
548 				}
549 
550 				// Draw main glyph (with any border) on top of shadow.
551 				for (int i = 0, n = parameter.renderCount; i < n; i++)
552 					shadowPixmap.drawPixmap(mainPixmap, Math.max(-parameter.shadowOffsetX, 0), Math.max(-parameter.shadowOffsetY, 0));
553 				mainPixmap.dispose();
554 				mainPixmap = shadowPixmap;
555 			} else if (parameter.borderWidth == 0) {
556 				// No shadow and no border, draw glyph additional times.
557 				for (int i = 0, n = parameter.renderCount - 1; i < n; i++)
558 					mainPixmap.drawPixmap(mainPixmap, 0, 0);
559 			}
560 		}
561 
562 		GlyphMetrics metrics = slot.getMetrics();
563 		Glyph glyph = new Glyph();
564 		glyph.id = c;
565 		glyph.width = mainPixmap.getWidth();
566 		glyph.height = mainPixmap.getHeight();
567 		glyph.xoffset = mainGlyph.getLeft();
568 		glyph.yoffset = parameter.flip ? -mainGlyph.getTop() + (int)baseLine : -(glyph.height - mainGlyph.getTop()) - (int)baseLine;
569 		glyph.xadvance = FreeType.toInt(metrics.getHoriAdvance()) + (int)parameter.borderWidth + parameter.spaceX;
570 
571 		if (bitmapped) {
572 			mainPixmap.setColor(Color.CLEAR);
573 			mainPixmap.fill();
574 			ByteBuffer buf = mainBitmap.getBuffer();
575 			int whiteIntBits = Color.WHITE.toIntBits();
576 			int clearIntBits = Color.CLEAR.toIntBits();
577 			for (int h = 0; h < glyph.height; h++) {
578 				int idx = h * mainBitmap.getPitch();
579 				for (int w = 0; w < (glyph.width + glyph.xoffset); w++) {
580 					int bit = (buf.get(idx + (w / 8)) >>> (7 - (w % 8))) & 1;
581 					mainPixmap.drawPixel(w, h, ((bit == 1) ? whiteIntBits : clearIntBits));
582 				}
583 			}
584 
585 		}
586 
587 		Rectangle rect = packer.pack(mainPixmap);
588 		glyph.page = packer.getPages().size - 1; // Glyph is always packed into the last page for now.
589 		glyph.srcX = (int)rect.x;
590 		glyph.srcY = (int)rect.y;
591 
592 		// If a page was added, create a new texture region for the incrementally added glyph.
593 		if (parameter.incremental && data.regions != null && data.regions.size <= glyph.page)
594 			packer.updateTextureRegions(data.regions, parameter.minFilter, parameter.magFilter, parameter.genMipMaps);
595 
596 		mainPixmap.dispose();
597 		mainGlyph.dispose();
598 
599 		return glyph;
600 	}
601 
602 	/** Cleans up all resources of the generator. Call this if you no longer use the generator. */
603 	@Override
dispose()604 	public void dispose () {
605 		face.dispose();
606 		library.dispose();
607 	}
608 
609 	/** Sets the maximum size that will be used when generating texture atlases for glyphs with <tt>generateData()</tt>. The
610 	 * default is 1024. By specifying {@link #NO_MAXIMUM}, the texture atlas will scale as needed.
611 	 *
612 	 * The power-of-two square texture size will be capped to the given <tt>texSize</tt>. It's recommended that a power-of-two
613 	 * value be used here.
614 	 *
615 	 * Multiple pages may be used to fit all the generated glyphs. You can query the resulting number of pages by calling
616 	 * <tt>bitmapFont.getRegions().length</tt> or <tt>freeTypeBitmapFontData.getTextureRegions().length</tt>.
617 	 *
618 	 * If PixmapPacker is specified when calling generateData, this parameter is ignored.
619 	 *
620 	 * @param texSize the maximum texture size for one page of glyphs */
setMaxTextureSize(int texSize)621 	public static void setMaxTextureSize (int texSize) {
622 		maxTextureSize = texSize;
623 	}
624 
625 	/** Returns the maximum texture size that will be used by generateData() when creating a texture atlas for the glyphs.
626 	 * @return the power-of-two max texture size */
getMaxTextureSize()627 	public static int getMaxTextureSize () {
628 		return maxTextureSize;
629 	}
630 
631 	/** {@link BitmapFontData} used for fonts generated via the {@link FreeTypeFontGenerator}. The texture storing the glyphs is
632 	 * held in memory, thus the {@link #getImagePaths()} and {@link #getFontFile()} methods will return null.
633 	 * @author mzechner
634 	 * @author Nathan Sweet */
635 	static public class FreeTypeBitmapFontData extends BitmapFontData implements Disposable {
636 		Array<TextureRegion> regions;
637 
638 		// Fields for incremental glyph generation.
639 		FreeTypeFontGenerator generator;
640 		FreeTypeFontParameter parameter;
641 		Stroker stroker;
642 		PixmapPacker packer;
643 		Array<Glyph> glyphs;
644 		private boolean dirty;
645 
646 		@Override
getGlyph(char ch)647 		public Glyph getGlyph (char ch) {
648 			Glyph glyph = super.getGlyph(ch);
649 			if (glyph == null && generator != null) {
650 				generator.setPixelSizes(0, parameter.size);
651 				float baseline = ((flipped ? -ascent : ascent) + capHeight) / scaleY;
652 				glyph = generator.createGlyph(ch, this, parameter, stroker, baseline, packer);
653 				if (glyph == null) return missingGlyph;
654 
655 				setGlyphRegion(glyph, regions.get(glyph.page));
656 				setGlyph(ch, glyph);
657 				glyphs.add(glyph);
658 				dirty = true;
659 
660 				Face face = generator.face;
661 				if (parameter.kerning) {
662 					int glyphIndex = face.getCharIndex(ch);
663 					for (int i = 0, n = glyphs.size; i < n; i++) {
664 						Glyph other = glyphs.get(i);
665 						int otherIndex = face.getCharIndex(other.id);
666 
667 						int kerning = face.getKerning(glyphIndex, otherIndex, 0);
668 						if (kerning != 0) glyph.setKerning(other.id, FreeType.toInt(kerning));
669 
670 						kerning = face.getKerning(otherIndex, glyphIndex, 0);
671 						if (kerning != 0) other.setKerning(ch, FreeType.toInt(kerning));
672 					}
673 				}
674 			}
675 			return glyph;
676 		}
677 
getGlyphs(GlyphRun run, CharSequence str, int start, int end, boolean tightBounds)678 		public void getGlyphs (GlyphRun run, CharSequence str, int start, int end, boolean tightBounds) {
679 			if (packer != null) packer.setPackToTexture(true); // All glyphs added after this are packed directly to the texture.
680 			super.getGlyphs(run, str, start, end, tightBounds);
681 			if (dirty) {
682 				dirty = false;
683 				packer.updateTextureRegions(regions, parameter.minFilter, parameter.magFilter, parameter.genMipMaps);
684 			}
685 		}
686 
687 		@Override
dispose()688 		public void dispose () {
689 			if (stroker != null) stroker.dispose();
690 			if (packer != null) packer.dispose();
691 		}
692 	}
693 
694 	/** Font smoothing algorithm. */
695 	public static enum Hinting {
696 		/** Disable hinting. Generated glyphs will look blurry. */
697 		None,
698 		/** Light hinting with fuzzy edges, but close to the original shape */
699 		Slight,
700 		/** Default hinting */
701 		Medium,
702 		/** Strong hinting with crisp edges at the expense of shape fidelity */
703 		Full
704 	}
705 
706 	/** Parameter container class that helps configure how {@link FreeTypeBitmapFontData} and {@link BitmapFont} instances are
707 	 * generated.
708 	 *
709 	 * The packer field is for advanced usage, where it is necessary to pack multiple BitmapFonts (i.e. styles, sizes, families)
710 	 * into a single Texture atlas. If no packer is specified, the generator will use its own PixmapPacker to pack the glyphs into
711 	 * a power-of-two sized texture, and the resulting {@link FreeTypeBitmapFontData} will have a valid {@link TextureRegion} which
712 	 * can be used to construct a new {@link BitmapFont}.
713 	 *
714 	 * @author siondream
715 	 * @author Nathan Sweet */
716 	public static class FreeTypeFontParameter {
717 		/** The size in pixels */
718 		public int size = 16;
719 		/** If true, font smoothing is disabled. */
720 		public boolean mono;
721 		/** Strength of hinting when smoothing is enabled */
722 		public Hinting hinting = Hinting.Medium;
723 		/** Foreground color (required for non-black borders) */
724 		public Color color = Color.WHITE;
725 		/** Glyph gamma. Values > 1 reduce antialiasing. */
726 		public float gamma = 1.8f;
727 		/** Number of times to render the glyph. Useful with a shadow or border, so it doesn't show through the glyph. */
728 		public int renderCount = 2;
729 		/** Border width in pixels, 0 to disable */
730 		public float borderWidth = 0;
731 		/** Border color; only used if borderWidth > 0 */
732 		public Color borderColor = Color.BLACK;
733 		/** true for straight (mitered), false for rounded borders */
734 		public boolean borderStraight = false;
735 		/** Values < 1 increase the border size. */
736 		public float borderGamma = 1.8f;
737 		/** Offset of text shadow on X axis in pixels, 0 to disable */
738 		public int shadowOffsetX = 0;
739 		/** Offset of text shadow on Y axis in pixels, 0 to disable */
740 		public int shadowOffsetY = 0;
741 		/** Shadow color; only used if shadowOffset > 0 */
742 		public Color shadowColor = new Color(0, 0, 0, 0.75f);
743 		/** Pixels to add to glyph spacing. Can be negative. */
744 		public int spaceX, spaceY;
745 		/** The characters the font should contain */
746 		public String characters = DEFAULT_CHARS;
747 		/** Whether the font should include kerning */
748 		public boolean kerning = true;
749 		/** The optional PixmapPacker to use */
750 		public PixmapPacker packer = null;
751 		/** Whether to flip the font vertically */
752 		public boolean flip = false;
753 		/** Whether to generate mip maps for the resulting texture */
754 		public boolean genMipMaps = false;
755 		/** Minification filter */
756 		public TextureFilter minFilter = TextureFilter.Nearest;
757 		/** Magnification filter */
758 		public TextureFilter magFilter = TextureFilter.Nearest;
759 		/** When true, glyphs are rendered on the fly to the font's glyph page textures as they are needed. The
760 		 * FreeTypeFontGenerator must not be disposed until the font is no longer needed. The FreeTypeBitmapFontData must be
761 		 * disposed (separately from the generator) when the font is no longer needed. The FreeTypeFontParameter should not be
762 		 * modified after creating a font. If a PixmapPacker is not specified, the font glyph page textures will use
763 		 * {@link FreeTypeFontGenerator#getMaxTextureSize()}. */
764 		public boolean incremental;
765 	}
766 }
767