/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.graphics.g2d.freetype; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.Pixmap.Format; import com.badlogic.gdx.graphics.Texture.TextureFilter; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.BitmapFont.BitmapFontData; import com.badlogic.gdx.graphics.g2d.BitmapFont.Glyph; import com.badlogic.gdx.graphics.g2d.GlyphLayout.GlyphRun; import com.badlogic.gdx.graphics.g2d.PixmapPacker; import com.badlogic.gdx.graphics.g2d.PixmapPacker.GuillotineStrategy; import com.badlogic.gdx.graphics.g2d.PixmapPacker.PackStrategy; import com.badlogic.gdx.graphics.g2d.PixmapPacker.SkylineStrategy; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.graphics.g2d.freetype.FreeType.Bitmap; import com.badlogic.gdx.graphics.g2d.freetype.FreeType.Face; import com.badlogic.gdx.graphics.g2d.freetype.FreeType.GlyphMetrics; import com.badlogic.gdx.graphics.g2d.freetype.FreeType.GlyphSlot; import com.badlogic.gdx.graphics.g2d.freetype.FreeType.Library; import com.badlogic.gdx.graphics.g2d.freetype.FreeType.SizeMetrics; import com.badlogic.gdx.graphics.g2d.freetype.FreeType.Stroker; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.BufferUtils; import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.StreamUtils; /** Generates {@link BitmapFont} and {@link BitmapFontData} instances from TrueType, OTF, and other FreeType supported fonts. * </p> * * Usage example: * * <pre> * FreeTypeFontGenerator gen = new FreeTypeFontGenerator(Gdx.files.internal("myfont.ttf")); * BitmapFont font = gen.generateFont(16); * gen.dispose(); * </pre> * * The generator has to be disposed once it is no longer used. The returned {@link BitmapFont} instances are managed by the user * and have to be disposed as usual. * * @author mzechner * @author Nathan Sweet * @author Rob Rendell */ public class FreeTypeFontGenerator implements Disposable { 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"; /** A hint to scale the texture as needed, without capping it at any maximum size */ static public final int NO_MAXIMUM = -1; /** The maximum texture size allowed by generateData, when storing in a texture atlas. Multiple texture pages will be created * if necessary. Default is 1024. * @see #setMaxTextureSize(int) */ static private int maxTextureSize = 1024; final Library library; final Face face; final String name; boolean bitmapped = false; private int pixelWidth, pixelHeight; /** Creates a new generator from the given font file. Uses {@link FileHandle#length()} to determine the file size. If the file * length could not be determined (it was 0), an extra copy of the font bytes is performed. Throws a * {@link GdxRuntimeException} if loading did not succeed. */ public FreeTypeFontGenerator (FileHandle fontFile) { name = fontFile.pathWithoutExtension(); int fileSize = (int)fontFile.length(); library = FreeType.initFreeType(); if (library == null) throw new GdxRuntimeException("Couldn't initialize FreeType"); ByteBuffer buffer; InputStream input = fontFile.read(); try { if (fileSize == 0) { // Copy to a byte[] to get the file size, then copy to the buffer. byte[] data = StreamUtils.copyStreamToByteArray(input, fileSize > 0 ? (int)(fileSize * 1.5f) : 1024 * 16); buffer = BufferUtils.newUnsafeByteBuffer(data.length); BufferUtils.copy(data, 0, buffer, data.length); } else { // Trust the specified file size. buffer = BufferUtils.newUnsafeByteBuffer(fileSize); StreamUtils.copyStream(input, buffer); } } catch (IOException ex) { throw new GdxRuntimeException(ex); } finally { StreamUtils.closeQuietly(input); } face = library.newMemoryFace(buffer, 0); if (face == null) throw new GdxRuntimeException("Couldn't create face for font: " + fontFile); if (checkForBitmapFont()) return; setPixelSizes(0, 15); } private int getLoadingFlags (FreeTypeFontParameter parameter) { int loadingFlags = FreeType.FT_LOAD_DEFAULT; switch (parameter.hinting) { case None: loadingFlags |= FreeType.FT_LOAD_NO_HINTING; break; case Slight: loadingFlags |= FreeType.FT_LOAD_TARGET_LIGHT; break; case Medium: loadingFlags |= FreeType.FT_LOAD_TARGET_NORMAL; break; case Full: loadingFlags |= FreeType.FT_LOAD_TARGET_MONO; break; case AutoSlight: loadingFlags |= FreeType.FT_LOAD_FORCE_AUTOHINT | FreeType.FT_LOAD_TARGET_LIGHT; break; case AutoMedium: loadingFlags |= FreeType.FT_LOAD_FORCE_AUTOHINT | FreeType.FT_LOAD_TARGET_NORMAL; break; case AutoFull: loadingFlags |= FreeType.FT_LOAD_FORCE_AUTOHINT | FreeType.FT_LOAD_TARGET_MONO; break; } return loadingFlags; } private boolean loadChar (int c) { return loadChar(c, FreeType.FT_LOAD_DEFAULT | FreeType.FT_LOAD_FORCE_AUTOHINT); } private boolean loadChar (int c, int flags) { return face.loadChar(c, flags); } private boolean checkForBitmapFont () { int faceFlags = face.getFaceFlags(); if (((faceFlags & FreeType.FT_FACE_FLAG_FIXED_SIZES) == FreeType.FT_FACE_FLAG_FIXED_SIZES) && ((faceFlags & FreeType.FT_FACE_FLAG_HORIZONTAL) == FreeType.FT_FACE_FLAG_HORIZONTAL)) { if (loadChar(32)) { GlyphSlot slot = face.getGlyph(); if (slot.getFormat() == 1651078259) { bitmapped = true; } } } return bitmapped; } public BitmapFont generateFont (FreeTypeFontParameter parameter) { return generateFont(parameter, new FreeTypeBitmapFontData()); } /** Generates a new {@link BitmapFont}. The size is expressed in pixels. Throws a GdxRuntimeException if the font could not be * generated. Using big sizes might cause such an exception. * @param parameter configures how the font is generated */ public BitmapFont generateFont (FreeTypeFontParameter parameter, FreeTypeBitmapFontData data) { generateData(parameter, data); if (data.regions == null && parameter.packer != null) { data.regions = new Array(); parameter.packer.updateTextureRegions(data.regions, parameter.minFilter, parameter.magFilter, parameter.genMipMaps); } BitmapFont font = new BitmapFont(data, data.regions, true); font.setOwnsTexture(parameter.packer == null); return font; } /** Uses ascender and descender of font to calculate real height that makes all glyphs to fit in given pixel size. Source: * http://nothings.org/stb/stb_truetype.h / stbtt_ScaleForPixelHeight */ public int scaleForPixelHeight (int height) { setPixelSizes(0, height); SizeMetrics fontMetrics = face.getSize().getMetrics(); int ascent = FreeType.toInt(fontMetrics.getAscender()); int descent = FreeType.toInt(fontMetrics.getDescender()); return height * height / (ascent - descent); } /** Uses max advance, ascender and descender of font to calculate real height that makes any n glyphs to fit in given pixel * width. * @param width the max width to fit (in pixels) * @param numChars max number of characters that to fill width */ public int scaleForPixelWidth (int width, int numChars) { SizeMetrics fontMetrics = face.getSize().getMetrics(); int advance = FreeType.toInt(fontMetrics.getMaxAdvance()); int ascent = FreeType.toInt(fontMetrics.getAscender()); int descent = FreeType.toInt(fontMetrics.getDescender()); int unscaledHeight = ascent - descent; int height = unscaledHeight * width / (advance * numChars); setPixelSizes(0, height); return height; } /** Uses max advance, ascender and descender of font to calculate real height that makes any n glyphs to fit in given pixel * width and height. * @param width the max width to fit (in pixels) * @param height the max height to fit (in pixels) * @param numChars max number of characters that to fill width */ public int scaleToFitSquare (int width, int height, int numChars) { return Math.min(scaleForPixelHeight(height), scaleForPixelWidth(width, numChars)); } public class GlyphAndBitmap { public Glyph glyph; public Bitmap bitmap; } /** Returns null if glyph was not found. If there is nothing to render, for example with various space characters, then bitmap * is null. */ public GlyphAndBitmap generateGlyphAndBitmap (int c, int size, boolean flip) { setPixelSizes(0, size); SizeMetrics fontMetrics = face.getSize().getMetrics(); int baseline = FreeType.toInt(fontMetrics.getAscender()); // Check if character exists in this font. // 0 means 'undefined character code' if (face.getCharIndex(c) == 0) { return null; } // Try to load character if (!loadChar(c)) { throw new GdxRuntimeException("Unable to load character!"); } GlyphSlot slot = face.getGlyph(); // Try to render to bitmap Bitmap bitmap; if (bitmapped) { bitmap = slot.getBitmap(); } else if (!slot.renderGlyph(FreeType.FT_RENDER_MODE_NORMAL)) { bitmap = null; } else { bitmap = slot.getBitmap(); } GlyphMetrics metrics = slot.getMetrics(); Glyph glyph = new Glyph(); if (bitmap != null) { glyph.width = bitmap.getWidth(); glyph.height = bitmap.getRows(); } else { glyph.width = 0; glyph.height = 0; } glyph.xoffset = slot.getBitmapLeft(); glyph.yoffset = flip ? -slot.getBitmapTop() + baseline : -(glyph.height - slot.getBitmapTop()) - baseline; glyph.xadvance = FreeType.toInt(metrics.getHoriAdvance()); glyph.srcX = 0; glyph.srcY = 0; glyph.id = c; GlyphAndBitmap result = new GlyphAndBitmap(); result.glyph = glyph; result.bitmap = bitmap; return result; } /** Generates a new {@link BitmapFontData} instance, expert usage only. Throws a GdxRuntimeException if something went wrong. * @param size the size in pixels */ public FreeTypeBitmapFontData generateData (int size) { FreeTypeFontParameter parameter = new FreeTypeFontParameter(); parameter.size = size; return generateData(parameter); } public FreeTypeBitmapFontData generateData (FreeTypeFontParameter parameter) { return generateData(parameter, new FreeTypeBitmapFontData()); } void setPixelSizes (int pixelWidth, int pixelHeight) { this.pixelWidth = pixelWidth; this.pixelHeight = pixelHeight; if (!bitmapped && !face.setPixelSizes(pixelWidth, pixelHeight)) throw new GdxRuntimeException("Couldn't set size for font"); } /** Generates a new {@link BitmapFontData} instance, expert usage only. Throws a GdxRuntimeException if something went wrong. * @param parameter configures how the font is generated */ public FreeTypeBitmapFontData generateData (FreeTypeFontParameter parameter, FreeTypeBitmapFontData data) { parameter = parameter == null ? new FreeTypeFontParameter() : parameter; char[] characters = parameter.characters.toCharArray(); int charactersLength = characters.length; boolean incremental = parameter.incremental; int flags = getLoadingFlags(parameter); setPixelSizes(0, parameter.size); // set general font data SizeMetrics fontMetrics = face.getSize().getMetrics(); data.flipped = parameter.flip; data.ascent = FreeType.toInt(fontMetrics.getAscender()); data.descent = FreeType.toInt(fontMetrics.getDescender()); data.lineHeight = FreeType.toInt(fontMetrics.getHeight()); float baseLine = data.ascent; // if bitmapped if (bitmapped && (data.lineHeight == 0)) { for (int c = 32; c < (32 + face.getNumGlyphs()); c++) { if (loadChar(c, flags)) { int lh = FreeType.toInt(face.getGlyph().getMetrics().getHeight()); data.lineHeight = (lh > data.lineHeight) ? lh : data.lineHeight; } } } data.lineHeight += parameter.spaceY; // determine space width if (loadChar(' ', flags) || loadChar('l', flags)) { data.spaceWidth = FreeType.toInt(face.getGlyph().getMetrics().getHoriAdvance()); } else { data.spaceWidth = face.getMaxAdvanceWidth(); // Possibly very wrong. } // determine x-height for (char xChar : data.xChars) { if (!loadChar(xChar, flags)) continue; data.xHeight = FreeType.toInt(face.getGlyph().getMetrics().getHeight()); break; } if (data.xHeight == 0) throw new GdxRuntimeException("No x-height character found in font"); // determine cap height for (char capChar : data.capChars) { if (!loadChar(capChar, flags)) continue; data.capHeight = FreeType.toInt(face.getGlyph().getMetrics().getHeight()); break; } if (!bitmapped && data.capHeight == 1) throw new GdxRuntimeException("No cap character found in font"); data.ascent -= data.capHeight; data.down = -data.lineHeight; if (parameter.flip) { data.ascent = -data.ascent; data.down = -data.down; } boolean ownsAtlas = false; PixmapPacker packer = parameter.packer; if (packer == null) { // Create a packer. int size; PackStrategy packStrategy; if (incremental) { size = maxTextureSize; packStrategy = new GuillotineStrategy(); } else { int maxGlyphHeight = (int)Math.ceil(data.lineHeight); size = MathUtils.nextPowerOfTwo((int)Math.sqrt(maxGlyphHeight * maxGlyphHeight * charactersLength)); if (maxTextureSize > 0) size = Math.min(size, maxTextureSize); packStrategy = new SkylineStrategy(); } ownsAtlas = true; packer = new PixmapPacker(size, size, Format.RGBA8888, 1, false, packStrategy); packer.setTransparentColor(parameter.color); packer.getTransparentColor().a = 0; if (parameter.borderWidth > 0) { packer.setTransparentColor(parameter.borderColor); packer.getTransparentColor().a = 0; } } if (incremental) data.glyphs = new Array(charactersLength + 32); Stroker stroker = null; if (parameter.borderWidth > 0) { stroker = library.createStroker(); stroker.set((int)(parameter.borderWidth * 64f), parameter.borderStraight ? FreeType.FT_STROKER_LINECAP_BUTT : FreeType.FT_STROKER_LINECAP_ROUND, parameter.borderStraight ? FreeType.FT_STROKER_LINEJOIN_MITER_FIXED : FreeType.FT_STROKER_LINEJOIN_ROUND, 0); } Glyph missingGlyph = createGlyph('\0', data, parameter, stroker, baseLine, packer); if (missingGlyph != null && missingGlyph.width != 0 && missingGlyph.height != 0) { data.setGlyph('\0', missingGlyph); if (incremental) data.glyphs.add(missingGlyph); } // Create glyphs largest height first for best packing. int[] heights = new int[charactersLength]; for (int i = 0, n = charactersLength; i < n; i++) { int height = loadChar(characters[i], flags) ? FreeType.toInt(face.getGlyph().getMetrics().getHeight()) : 0; heights[i] = height; } int heightsCount = heights.length; while (heightsCount > 0) { int best = 0, maxHeight = heights[0]; for (int i = 1; i < heightsCount; i++) { int height = heights[i]; if (height > maxHeight) { maxHeight = height; best = i; } } char c = characters[best]; Glyph glyph = createGlyph(c, data, parameter, stroker, baseLine, packer); if (glyph != null) { data.setGlyph(c, glyph); if (incremental) data.glyphs.add(glyph); } heightsCount--; heights[best] = heights[heightsCount]; char tmpChar = characters[best]; characters[best] = characters[heightsCount]; characters[heightsCount] = tmpChar; } if (stroker != null && !incremental) stroker.dispose(); if (incremental) { data.generator = this; data.parameter = parameter; data.stroker = stroker; data.packer = packer; } // Generate kerning. parameter.kerning &= face.hasKerning(); if (parameter.kerning) { for (int i = 0; i < charactersLength; i++) { char firstChar = characters[i]; Glyph first = data.getGlyph(firstChar); if (first == null) continue; int firstIndex = face.getCharIndex(firstChar); for (int ii = i; ii < charactersLength; ii++) { char secondChar = characters[ii]; Glyph second = data.getGlyph(secondChar); if (second == null) continue; int secondIndex = face.getCharIndex(secondChar); int kerning = face.getKerning(firstIndex, secondIndex, 0); if (kerning != 0) first.setKerning(secondChar, FreeType.toInt(kerning)); kerning = face.getKerning(secondIndex, firstIndex, 0); if (kerning != 0) second.setKerning(firstChar, FreeType.toInt(kerning)); } } } // Generate texture regions. if (ownsAtlas) { data.regions = new Array(); packer.updateTextureRegions(data.regions, parameter.minFilter, parameter.magFilter, parameter.genMipMaps); } // Set space glyph. Glyph spaceGlyph = data.getGlyph(' '); if (spaceGlyph == null) { spaceGlyph = new Glyph(); spaceGlyph.xadvance = (int)data.spaceWidth + parameter.spaceX; spaceGlyph.id = (int)' '; data.setGlyph(' ', spaceGlyph); } if (spaceGlyph.width == 0) spaceGlyph.width = (int)(spaceGlyph.xadvance + data.padRight); return data; } /** @return null if glyph was not found. */ Glyph createGlyph (char c, FreeTypeBitmapFontData data, FreeTypeFontParameter parameter, Stroker stroker, float baseLine, PixmapPacker packer) { boolean missing = face.getCharIndex(c) == 0 && c != 0; if (missing) return null; if (!loadChar(c, getLoadingFlags(parameter))) return null; GlyphSlot slot = face.getGlyph(); FreeType.Glyph mainGlyph = slot.getGlyph(); try { mainGlyph.toBitmap(parameter.mono ? FreeType.FT_RENDER_MODE_MONO : FreeType.FT_RENDER_MODE_NORMAL); } catch (GdxRuntimeException e) { mainGlyph.dispose(); Gdx.app.log("FreeTypeFontGenerator", "Couldn't render char: " + c); return null; } Bitmap mainBitmap = mainGlyph.getBitmap(); Pixmap mainPixmap = mainBitmap.getPixmap(Format.RGBA8888, parameter.color, parameter.gamma); if (mainBitmap.getWidth() != 0 && mainBitmap.getRows() != 0) { int offsetX = 0, offsetY = 0; if (parameter.borderWidth > 0) { // execute stroker; this generates a glyph "extended" along the outline int top = mainGlyph.getTop(), left = mainGlyph.getLeft(); FreeType.Glyph borderGlyph = slot.getGlyph(); borderGlyph.strokeBorder(stroker, false); borderGlyph.toBitmap(parameter.mono ? FreeType.FT_RENDER_MODE_MONO : FreeType.FT_RENDER_MODE_NORMAL); offsetX = left - borderGlyph.getLeft(); offsetY = -(top - borderGlyph.getTop()); // Render border (pixmap is bigger than main). Bitmap borderBitmap = borderGlyph.getBitmap(); Pixmap borderPixmap = borderBitmap.getPixmap(Format.RGBA8888, parameter.borderColor, parameter.borderGamma); // Draw main glyph on top of border. for (int i = 0, n = parameter.renderCount; i < n; i++) borderPixmap.drawPixmap(mainPixmap, offsetX, offsetY); mainPixmap.dispose(); mainGlyph.dispose(); mainPixmap = borderPixmap; mainGlyph = borderGlyph; } if (parameter.shadowOffsetX != 0 || parameter.shadowOffsetY != 0) { int mainW = mainPixmap.getWidth(), mainH = mainPixmap.getHeight(); int shadowOffsetX = Math.max(parameter.shadowOffsetX, 0), shadowOffsetY = Math.max(parameter.shadowOffsetY, 0); int shadowW = mainW + Math.abs(parameter.shadowOffsetX), shadowH = mainH + Math.abs(parameter.shadowOffsetY); Pixmap shadowPixmap = new Pixmap(shadowW, shadowH, mainPixmap.getFormat()); Color shadowColor = parameter.shadowColor; float a = shadowColor.a; if (a != 0) { byte r = (byte)(shadowColor.r * 255), g = (byte)(shadowColor.g * 255), b = (byte)(shadowColor.b * 255); ByteBuffer mainPixels = mainPixmap.getPixels(); ByteBuffer shadowPixels = shadowPixmap.getPixels(); for (int y = 0; y < mainH; y++) { int shadowRow = shadowW * (y + shadowOffsetY) + shadowOffsetX; for (int x = 0; x < mainW; x++) { int mainPixel = (mainW * y + x) * 4; byte mainA = mainPixels.get(mainPixel + 3); if (mainA == 0) continue; int shadowPixel = (shadowRow + x) * 4; shadowPixels.put(shadowPixel, r); shadowPixels.put(shadowPixel + 1, g); shadowPixels.put(shadowPixel + 2, b); shadowPixels.put(shadowPixel + 3, (byte)((mainA & 0xff) * a)); } } } // Draw main glyph (with any border) on top of shadow. for (int i = 0, n = parameter.renderCount; i < n; i++) shadowPixmap.drawPixmap(mainPixmap, Math.max(-parameter.shadowOffsetX, 0), Math.max(-parameter.shadowOffsetY, 0)); mainPixmap.dispose(); mainPixmap = shadowPixmap; } else if (parameter.borderWidth == 0) { // No shadow and no border, draw glyph additional times. for (int i = 0, n = parameter.renderCount - 1; i < n; i++) mainPixmap.drawPixmap(mainPixmap, 0, 0); } } GlyphMetrics metrics = slot.getMetrics(); Glyph glyph = new Glyph(); glyph.id = c; glyph.width = mainPixmap.getWidth(); glyph.height = mainPixmap.getHeight(); glyph.xoffset = mainGlyph.getLeft(); glyph.yoffset = parameter.flip ? -mainGlyph.getTop() + (int)baseLine : -(glyph.height - mainGlyph.getTop()) - (int)baseLine; glyph.xadvance = FreeType.toInt(metrics.getHoriAdvance()) + (int)parameter.borderWidth + parameter.spaceX; if (bitmapped) { mainPixmap.setColor(Color.CLEAR); mainPixmap.fill(); ByteBuffer buf = mainBitmap.getBuffer(); int whiteIntBits = Color.WHITE.toIntBits(); int clearIntBits = Color.CLEAR.toIntBits(); for (int h = 0; h < glyph.height; h++) { int idx = h * mainBitmap.getPitch(); for (int w = 0; w < (glyph.width + glyph.xoffset); w++) { int bit = (buf.get(idx + (w / 8)) >>> (7 - (w % 8))) & 1; mainPixmap.drawPixel(w, h, ((bit == 1) ? whiteIntBits : clearIntBits)); } } } Rectangle rect = packer.pack(mainPixmap); glyph.page = packer.getPages().size - 1; // Glyph is always packed into the last page for now. glyph.srcX = (int)rect.x; glyph.srcY = (int)rect.y; // If a page was added, create a new texture region for the incrementally added glyph. if (parameter.incremental && data.regions != null && data.regions.size <= glyph.page) packer.updateTextureRegions(data.regions, parameter.minFilter, parameter.magFilter, parameter.genMipMaps); mainPixmap.dispose(); mainGlyph.dispose(); return glyph; } /** Cleans up all resources of the generator. Call this if you no longer use the generator. */ @Override public void dispose () { face.dispose(); library.dispose(); } /** Sets the maximum size that will be used when generating texture atlases for glyphs with <tt>generateData()</tt>. The * default is 1024. By specifying {@link #NO_MAXIMUM}, the texture atlas will scale as needed. * * The power-of-two square texture size will be capped to the given <tt>texSize</tt>. It's recommended that a power-of-two * value be used here. * * Multiple pages may be used to fit all the generated glyphs. You can query the resulting number of pages by calling * <tt>bitmapFont.getRegions().length</tt> or <tt>freeTypeBitmapFontData.getTextureRegions().length</tt>. * * If PixmapPacker is specified when calling generateData, this parameter is ignored. * * @param texSize the maximum texture size for one page of glyphs */ public static void setMaxTextureSize (int texSize) { maxTextureSize = texSize; } /** Returns the maximum texture size that will be used by generateData() when creating a texture atlas for the glyphs. * @return the power-of-two max texture size */ public static int getMaxTextureSize () { return maxTextureSize; } /** {@link BitmapFontData} used for fonts generated via the {@link FreeTypeFontGenerator}. The texture storing the glyphs is * held in memory, thus the {@link #getImagePaths()} and {@link #getFontFile()} methods will return null. * @author mzechner * @author Nathan Sweet */ static public class FreeTypeBitmapFontData extends BitmapFontData implements Disposable { Array<TextureRegion> regions; // Fields for incremental glyph generation. FreeTypeFontGenerator generator; FreeTypeFontParameter parameter; Stroker stroker; PixmapPacker packer; Array<Glyph> glyphs; private boolean dirty; @Override public Glyph getGlyph (char ch) { Glyph glyph = super.getGlyph(ch); if (glyph == null && generator != null) { generator.setPixelSizes(0, parameter.size); float baseline = ((flipped ? -ascent : ascent) + capHeight) / scaleY; glyph = generator.createGlyph(ch, this, parameter, stroker, baseline, packer); if (glyph == null) return missingGlyph; setGlyphRegion(glyph, regions.get(glyph.page)); setGlyph(ch, glyph); glyphs.add(glyph); dirty = true; Face face = generator.face; if (parameter.kerning) { int glyphIndex = face.getCharIndex(ch); for (int i = 0, n = glyphs.size; i < n; i++) { Glyph other = glyphs.get(i); int otherIndex = face.getCharIndex(other.id); int kerning = face.getKerning(glyphIndex, otherIndex, 0); if (kerning != 0) glyph.setKerning(other.id, FreeType.toInt(kerning)); kerning = face.getKerning(otherIndex, glyphIndex, 0); if (kerning != 0) other.setKerning(ch, FreeType.toInt(kerning)); } } } return glyph; } public void getGlyphs (GlyphRun run, CharSequence str, int start, int end, boolean tightBounds) { if (packer != null) packer.setPackToTexture(true); // All glyphs added after this are packed directly to the texture. super.getGlyphs(run, str, start, end, tightBounds); if (dirty) { dirty = false; packer.updateTextureRegions(regions, parameter.minFilter, parameter.magFilter, parameter.genMipMaps); } } @Override public void dispose () { if (stroker != null) stroker.dispose(); if (packer != null) packer.dispose(); } } /** Font smoothing algorithm. */ public static enum Hinting { /** Disable hinting. Generated glyphs will look blurry. */ None, /** Light hinting with fuzzy edges, but close to the original shape */ Slight, /** Average hinting */ Medium, /** Strong hinting with crisp edges at the expense of shape fidelity */ Full, /** Light hinting with fuzzy edges, but close to the original shape. Uses the FreeType auto-hinter. */ AutoSlight, /** Average hinting. Uses the FreeType auto-hinter. */ AutoMedium, /** Strong hinting with crisp edges at the expense of shape fidelity. Uses the FreeType auto-hinter. */ AutoFull, } /** Parameter container class that helps configure how {@link FreeTypeBitmapFontData} and {@link BitmapFont} instances are * generated. * * The packer field is for advanced usage, where it is necessary to pack multiple BitmapFonts (i.e. styles, sizes, families) * into a single Texture atlas. If no packer is specified, the generator will use its own PixmapPacker to pack the glyphs into * a power-of-two sized texture, and the resulting {@link FreeTypeBitmapFontData} will have a valid {@link TextureRegion} which * can be used to construct a new {@link BitmapFont}. * * @author siondream * @author Nathan Sweet */ public static class FreeTypeFontParameter { /** The size in pixels */ public int size = 16; /** If true, font smoothing is disabled. */ public boolean mono; /** Strength of hinting */ public Hinting hinting = Hinting.AutoMedium; /** Foreground color (required for non-black borders) */ public Color color = Color.WHITE; /** Glyph gamma. Values > 1 reduce antialiasing. */ public float gamma = 1.8f; /** Number of times to render the glyph. Useful with a shadow or border, so it doesn't show through the glyph. */ public int renderCount = 2; /** Border width in pixels, 0 to disable */ public float borderWidth = 0; /** Border color; only used if borderWidth > 0 */ public Color borderColor = Color.BLACK; /** true for straight (mitered), false for rounded borders */ public boolean borderStraight = false; /** Values < 1 increase the border size. */ public float borderGamma = 1.8f; /** Offset of text shadow on X axis in pixels, 0 to disable */ public int shadowOffsetX = 0; /** Offset of text shadow on Y axis in pixels, 0 to disable */ public int shadowOffsetY = 0; /** Shadow color; only used if shadowOffset > 0. If alpha component is 0, no shadow is drawn but characters are still offset * by shadowOffset. */ public Color shadowColor = new Color(0, 0, 0, 0.75f); /** Pixels to add to glyph spacing. Can be negative. */ public int spaceX, spaceY; /** The characters the font should contain */ public String characters = DEFAULT_CHARS; /** Whether the font should include kerning */ public boolean kerning = true; /** The optional PixmapPacker to use */ public PixmapPacker packer = null; /** Whether to flip the font vertically */ public boolean flip = false; /** Whether to generate mip maps for the resulting texture */ public boolean genMipMaps = false; /** Minification filter */ public TextureFilter minFilter = TextureFilter.Nearest; /** Magnification filter */ public TextureFilter magFilter = TextureFilter.Nearest; /** When true, glyphs are rendered on the fly to the font's glyph page textures as they are needed. The * FreeTypeFontGenerator must not be disposed until the font is no longer needed. The FreeTypeBitmapFontData must be * disposed (separately from the generator) when the font is no longer needed. The FreeTypeFontParameter should not be * modified after creating a font. If a PixmapPacker is not specified, the font glyph page textures will use * {@link FreeTypeFontGenerator#getMaxTextureSize()}. */ public boolean incremental; } }