Created
May 3, 2013 18:12
-
-
Save mattdesl/5512218 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Of course there are better ways to abstract this process for many styles/sizes. | |
// Here is just a brief example without any abstraction. | |
////// 1. Use PixmapPacker for tight bin packing.. we can adjust spacing/size | |
PixmapPacker atlas = new PixmapPacker(512, 512, Format.RGBA8888, 2, false); | |
//generate regular fonts | |
//prefix string doesn't really matter as long as its unique | |
TTFGenerator ttf = new TTFGenerator(Gdx.files.internal("data/Roboto-Regular.ttf")); | |
FreeTypeBitmapFontData d_reg16 = ttf.generateData(16, TTFGenerator.DEFAULT_CHARS, false, atlas, "16_"); | |
FreeTypeBitmapFontData d_reg18 = ttf.generateData(18, TTFGenerator.DEFAULT_CHARS, false, atlas, "18_"); | |
ttf.dispose(); | |
//generate bold fonts | |
ttf = new TTFGenerator(Gdx.files.internal("data/Roboto-Bold.ttf")); | |
FreeTypeBitmapFontData d_bold16 = ttf.generateData(16, TTFGenerator.DEFAULT_CHARS, false, atlas, "bold_16_"); | |
FreeTypeBitmapFontData d_bold18 = ttf.generateData(18, TTFGenerator.DEFAULT_CHARS, false, atlas, "bold_18_"); | |
ttf.dispose(); | |
////// 2. Upload pixel data to GPU | |
//get first page... this only works with 1 page so far | |
Page p = atlas.getPages().get(0); | |
//generate texture from pixmap packer | |
Texture tex = new Texture(p.getPixmap(), p.getPixmap().getFormat(), false); | |
tex.setFilter(TextureFilter.Nearest, TextureFilter.Nearest); | |
//dispose pixmap packer | |
atlas.dispose(); | |
//texture region for our data | |
TextureRegion region = new TextureRegion(tex); | |
d_reg16.region = region; | |
d_reg18.region = region; | |
d_bold16.region = region; | |
d_bold18.region = region; | |
////// 3. Create fonts | |
BitmapFont reg16 = new BitmapFont(d_reg16, region, false); | |
BitmapFont reg18 = new BitmapFont(d_reg18, region, false); | |
BitmapFont bold16 = new BitmapFont(d_bold16, region, false); | |
BitmapFont bold18 = new BitmapFont(d_bold18, region, false); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package mdesl.line2dx.test; | |
/******************************************************************************* | |
* 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. | |
******************************************************************************/ | |
import com.badlogic.gdx.Gdx; | |
import com.badlogic.gdx.files.FileHandle; | |
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.PixmapPacker; | |
import com.badlogic.gdx.graphics.g2d.TextureAtlas; | |
import com.badlogic.gdx.graphics.g2d.TextureRegion; | |
import com.badlogic.gdx.graphics.g2d.freetype.FreeType; | |
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.FreeTypeFontGenerator; | |
import com.badlogic.gdx.math.MathUtils; | |
import com.badlogic.gdx.math.Rectangle; | |
import com.badlogic.gdx.utils.Disposable; | |
import com.badlogic.gdx.utils.GdxRuntimeException; | |
/** Generates {@link BitmapFont} and {@link BitmapFontData} instances from TrueType font files.</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 */ | |
public class TTFGenerator implements Disposable { | |
public static final String DEFAULT_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890\"!`?'.,;:()[]{}<>|/@\\^$-%+=#_&~*\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"; | |
final Library library; | |
final Face face; | |
/** Creates a new generator from the given TrueType font file. Throws a {@link GdxRuntimeException} in case loading did not | |
* succeed. | |
* @param font the {@link FileHandle} to the TrueType font file */ | |
public TTFGenerator (FileHandle font) { | |
library = FreeType.initFreeType(); | |
if (library == null) throw new GdxRuntimeException("Couldn't initialize FreeType"); | |
face = FreeType.newFace(library, font, 0); | |
if (face == null) throw new GdxRuntimeException("Couldn't create face for font '" + font + "'"); | |
if (!FreeType.setPixelSizes(face, 0, 15)) throw new GdxRuntimeException("Couldn't set size for font '" + font + "'"); | |
} | |
/** Generates a new {@link BitmapFont}, containing glyphs for the given characters. The size is expressed in pixels. Throws a | |
* GdxRuntimeException in case the font could not be generated. Using big sizes might cause such an exception. All characters | |
* need to fit onto a single texture. | |
* @param size the size in pixels | |
* @param characters the characters the font should contain | |
* @param flip whether to flip the font horizontally, see {@link BitmapFont#BitmapFont(FileHandle, TextureRegion, boolean)} */ | |
public BitmapFont generateFont (int size, String characters, boolean flip) { | |
FreeTypeBitmapFontData data = generateData(size, characters, flip, null, null); | |
BitmapFont font = new BitmapFont(data, data.getTextureRegion(), false); | |
font.setOwnsTexture(true); | |
return font; | |
} | |
/** Generates a new {@link BitmapFont}. The size is expressed in pixels. Throws a GdxRuntimeException in case the font could not | |
* be generated. Using big sizes might cause such an exception. All characters need to fit onto a single texture. | |
* | |
* @param size the size of the font in pixels */ | |
public BitmapFont generateFont (int size) { | |
return generateFont(size, DEFAULT_CHARS, false); | |
} | |
/** 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 size) { | |
if (!FreeType.setPixelSizes(face, 0, size)) throw new GdxRuntimeException("Couldn't set size for font"); | |
SizeMetrics fontMetrics = face.getSize().getMetrics(); | |
int ascent = FreeType.toInt(fontMetrics.getAscender()); | |
int descent = FreeType.toInt(fontMetrics.getDescender()); | |
return size * size / (ascent - descent); | |
} | |
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) { | |
if (!FreeType.setPixelSizes(face, 0, size)) throw new GdxRuntimeException("Couldn't set size for font"); | |
SizeMetrics fontMetrics = face.getSize().getMetrics(); | |
int baseline = FreeType.toInt(fontMetrics.getAscender()); | |
// Check if character exists in this font. | |
// 0 means 'undefined character code' | |
if (FreeType.getCharIndex(face, c) == 0) { | |
return null; | |
} | |
// Try to load character | |
if (!FreeType.loadChar(face, c, FreeType.FT_LOAD_DEFAULT)) { | |
throw new GdxRuntimeException("Unable to load character!"); | |
} | |
GlyphSlot slot = face.getGlyph(); | |
// Try to render to bitmap | |
Bitmap bitmap; | |
if (!FreeType.renderGlyph(slot, FreeType.FT_RENDER_MODE_LIGHT)) { | |
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; | |
GlyphAndBitmap result = new GlyphAndBitmap(); | |
result.glyph = glyph; | |
result.bitmap = bitmap; | |
return result; | |
} | |
/** Generates a new {@link BitmapFontData} instance, expert usage only. Throws a GdxRuntimeException in case something went | |
* wrong. | |
* @param size the size in pixels */ | |
public FreeTypeBitmapFontData generateData (int size) { | |
return generateData(size, DEFAULT_CHARS, false, null, null); | |
} | |
public static int count; | |
/** Generates a new {@link BitmapFontData} instance, expert usage only. Throws a GdxRuntimeException in case something went | |
* wrong. | |
* | |
* @param size the size in pixels | |
* @param characters the characters the font should contain | |
* @param flip whether to flip the font horizontally, see {@link BitmapFont#BitmapFont(FileHandle, TextureRegion, boolean)} */ | |
public FreeTypeBitmapFontData generateData (int size, String characters, boolean flip, PixmapPacker atlas, String packPrefix) { | |
FreeTypeBitmapFontData data = new FreeTypeBitmapFontData(); | |
if (!FreeType.setPixelSizes(face, 0, size)) throw new GdxRuntimeException("Couldn't set size for font"); | |
// set general font data | |
SizeMetrics fontMetrics = face.getSize().getMetrics(); | |
data.flipped = flip; | |
data.ascent = FreeType.toInt(fontMetrics.getAscender()); | |
data.descent = FreeType.toInt(fontMetrics.getDescender()); | |
data.lineHeight = FreeType.toInt(fontMetrics.getHeight()); | |
float baseLine = data.ascent; | |
// determine space width and set glyph | |
if (FreeType.loadChar(face, ' ', FreeType.FT_LOAD_DEFAULT)) { | |
data.spaceWidth = FreeType.toInt(face.getGlyph().getMetrics().getHoriAdvance()); | |
} else { | |
data.spaceWidth = face.getMaxAdvanceWidth(); // FIXME possibly very wrong :) | |
} | |
Glyph spaceGlyph = new Glyph(); | |
spaceGlyph.xadvance = (int)data.spaceWidth; | |
data.setGlyph(' ', spaceGlyph); | |
// determine x-height | |
for (char xChar : BitmapFont.xChars) { | |
if (!FreeType.loadChar(face, xChar, FreeType.FT_LOAD_DEFAULT)) continue; | |
data.xHeight = FreeType.toInt(face.getGlyph().getMetrics().getHeight()); | |
break; | |
} | |
if (data.xHeight == 0) throw new GdxRuntimeException("No x-height character found in font"); | |
for (char capChar : BitmapFont.capChars) { | |
if (!FreeType.loadChar(face, capChar, FreeType.FT_LOAD_DEFAULT)) continue; | |
data.capHeight = FreeType.toInt(face.getGlyph().getMetrics().getHeight()); | |
break; | |
} | |
// determine cap height | |
if (data.capHeight == 1) throw new GdxRuntimeException("No cap character found in font"); | |
data.ascent = data.ascent - data.capHeight; | |
data.down = -data.lineHeight; | |
if (flip) { | |
data.ascent = -data.ascent; | |
data.down = -data.down; | |
} | |
// generate the glyphs | |
int maxGlyphHeight = (int)Math.ceil(data.lineHeight); | |
int pageWidth = MathUtils.nextPowerOfTwo((int)Math.sqrt(maxGlyphHeight * maxGlyphHeight * characters.length())); | |
boolean ownsAtlas = false; | |
if (atlas==null) { | |
ownsAtlas = true; | |
atlas = new PixmapPacker(pageWidth, pageWidth, Format.RGBA8888, 2, false); | |
} | |
packPrefix = packPrefix!=null ? packPrefix : ""; | |
for (int i = 0; i < characters.length(); i++) { | |
char c = characters.charAt(i); | |
if (!FreeType.loadChar(face, c, FreeType.FT_LOAD_DEFAULT)) { | |
Gdx.app.log("FreeTypeFontGenerator", "Couldn't load char '" + c + "'"); | |
continue; | |
} | |
if (!FreeType.renderGlyph(face.getGlyph(), FreeType.FT_RENDER_MODE_NORMAL)) { | |
Gdx.app.log("FreeTypeFontGenerator", "Couldn't render char '" + c + "'"); | |
continue; | |
} | |
GlyphSlot slot = face.getGlyph(); | |
GlyphMetrics metrics = slot.getMetrics(); | |
Bitmap bitmap = slot.getBitmap(); | |
Pixmap pixmap = bitmap.getPixmap(Format.RGBA8888); | |
Rectangle rect = atlas.pack(packPrefix + c, pixmap); | |
count++; | |
Glyph glyph = new Glyph(); | |
glyph.width = pixmap.getWidth(); | |
glyph.height = pixmap.getHeight(); | |
glyph.xoffset = slot.getBitmapLeft(); | |
glyph.yoffset = flip ? -slot.getBitmapTop() + (int)baseLine : -(glyph.height - slot.getBitmapTop()) - (int)baseLine; | |
glyph.xadvance = FreeType.toInt(metrics.getHoriAdvance()); | |
glyph.srcX = (int)rect.x; | |
glyph.srcY = (int)rect.y; | |
data.setGlyph(c, glyph); | |
pixmap.dispose(); | |
} | |
// generate kerning | |
for (int i = 0; i < characters.length(); i++) { | |
for (int j = 0; j < characters.length(); j++) { | |
char firstChar = characters.charAt(i); | |
Glyph first = data.getGlyph(firstChar); | |
if (first == null) continue; | |
char secondChar = characters.charAt(j); | |
Glyph second = data.getGlyph(secondChar); | |
if (second == null) continue; | |
int kerning = FreeType.getKerning(face, FreeType.getCharIndex(face, firstChar), | |
FreeType.getCharIndex(face, secondChar), 0); | |
if (kerning == 0) continue; | |
first.setKerning(secondChar, FreeType.toInt(kerning)); | |
} | |
} | |
if (ownsAtlas) { | |
TextureAtlas textureAtlas = atlas.generateTextureAtlas(TextureFilter.Nearest, TextureFilter.Nearest, false); | |
data.region = new TextureRegion(textureAtlas.getRegions().get(0).getTexture()); | |
} | |
return data; | |
} | |
/** Cleans up all resources of the generator. Call this if you no longer use the generator. */ | |
@Override | |
public void dispose () { | |
FreeType.doneFace(face); | |
FreeType.doneFreeType(library); | |
} | |
/** {@link BitmapFontData} used for fonts generated via the {@link FreeTypeFontGenerator}. The texture storing the glyphs is | |
* held in memory, thus the {@link #getImagePath()} and {@link #getFontFile()} methods will return null. | |
* | |
* @author mzechner */ | |
public static class FreeTypeBitmapFontData extends BitmapFontData { | |
public TextureRegion region; | |
public TextureRegion getTextureRegion () { | |
return region; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment