Package com.badlogic.gdx.graphics.g2d.freetype

Source Code of com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator

/*******************************************************************************
* 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 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;
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.PixmapPacker.Page;
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.glutils.PixmapTextureData;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.GdxRuntimeException;

import java.nio.ByteBuffer;

/** Generates {@link BitmapFont} and {@link BitmapFontData} instances from TrueType font files.</p>
*
* Usage example:
*
* <pre>
* FreeTypeFontGenerator gen = new FreeTypeFontGenerator(Gdx.files.internal(&quot;myfont.ttf&quot;));
* 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 FreeTypeFontGenerator 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;
  final String filePath;
  boolean bitmapped = false;

  /** The maximum texture size allowed by generateData, when storing in a texture atlas. Multiple texture pages will be created if
   * necessary. */
  private static int maxTextureSize = 1024;

  /** A hint to scale the texture as needed, without capping it at any maximum size */
  public static final int NO_MAXIMUM = -1;

  /** Sets the maximum size that will be used when generating texture atlases for glyphs with <tt>generateData()</tt>. The default
   * is 1024. By specifying 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;
  }

  /** 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 FreeTypeFontGenerator (FileHandle font) {
    filePath = font.pathWithoutExtension();
    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 (checkForBitmapFont()) {
      return;
    }
    if (!FreeType.setPixelSizes(face, 0, 15)) throw new GdxRuntimeException("Couldn't set size for font '" + font + "'");
  }

  private boolean checkForBitmapFont () {
    if (((face.getFaceFlags() & FreeType.FT_FACE_FLAG_FIXED_SIZES) == FreeType.FT_FACE_FLAG_FIXED_SIZES)
      && ((face.getFaceFlags() & FreeType.FT_FACE_FLAG_HORIZONTAL) == FreeType.FT_FACE_FLAG_HORIZONTAL)) {
      if (FreeType.loadChar(face, 32, FreeType.FT_LOAD_DEFAULT)) {
        GlyphSlot slot = face.getGlyph();
        if (slot.getFormat() == 1651078259) {
          bitmapped = true;
        }
      }
    }
    return bitmapped;
  }

  /** 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)}
   * @deprecated use {@link #generateFont(FreeTypeFontParameter)} instead */
  public BitmapFont generateFont (int size, String characters, boolean flip) {
    FreeTypeBitmapFontData data = generateData(size, characters, flip, null);
    BitmapFont font = new BitmapFont(data, data.getTextureRegions(), 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
   * @deprecated use {@link #generateFont(FreeTypeFontParameter)} instead */
  public BitmapFont generateFont (int size) {
    return generateFont(size, DEFAULT_CHARS, false);
  }

  /** 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 parameter configures how the font is generated */
  public BitmapFont generateFont (FreeTypeFontParameter parameter) {
    FreeTypeBitmapFontData data = generateData(parameter);
    BitmapFont font = new BitmapFont(data, data.getTextureRegions(), false);
    font.setOwnsTexture(true);
    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 size) {
    if (!bitmapped && !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 (!bitmapped && !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 (bitmapped) {
      bitmap = slot.getBitmap();
    } else 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;
    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 in case something went
   * wrong.
   * @param size the size in pixels */
  public FreeTypeBitmapFontData generateData (int size) {
    return generateData(size, DEFAULT_CHARS, false, null);
  }

  /** 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)}
   * @deprecated use {@link #generateData(FreeTypeFontParameter)} instead */
  public FreeTypeBitmapFontData generateData (int size, String characters, boolean flip) {
    return generateData(size, characters, flip, null);
  }

  /** 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)}
   * @param packer the optional PixmapPacker to use
   * @deprecated use {@link #generateData(FreeTypeFontParameter)} instead */
  public FreeTypeBitmapFontData generateData (int size, String characters, boolean flip, PixmapPacker packer) {
    FreeTypeFontParameter parameter = new FreeTypeFontParameter();
    parameter.size = size;
    parameter.characters = characters;
    parameter.flip = flip;
    parameter.packer = packer;
    return generateData(parameter);
  }

  /** Generates a new {@link BitmapFontData} instance, expert usage only. Throws a GdxRuntimeException in case something went
   * wrong.
   *
   * @param parameter configures how the font is generated */
  public FreeTypeBitmapFontData generateData (FreeTypeFontParameter parameter) {
    parameter = parameter == null ? new FreeTypeFontParameter() : parameter;

    FreeTypeBitmapFontData data = new FreeTypeBitmapFontData();
    if (!bitmapped && !FreeType.setPixelSizes(face, 0, parameter.size))
      throw new GdxRuntimeException("Couldn't set size for font");

    // 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 (FreeType.loadChar(face, c, FreeType.FT_LOAD_DEFAULT)) {
          int lh = FreeType.toInt(face.getGlyph().getMetrics().getHeight());
          data.lineHeight = (lh > data.lineHeight) ? lh : data.lineHeight;
        }
      }
    }

    // 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;
    spaceGlyph.id = (int)' ';
    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 (!bitmapped && data.capHeight == 1) throw new GdxRuntimeException("No cap character found in font");
    data.ascent = 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) {
      // generate the glyphs
      int maxGlyphHeight = (int)Math.ceil(data.lineHeight);
      int pageWidth = MathUtils
        .nextPowerOfTwo((int)Math.sqrt(maxGlyphHeight * maxGlyphHeight * parameter.characters.length()));

      if (maxTextureSize > 0) pageWidth = Math.min(pageWidth, maxTextureSize);

      ownsAtlas = true;
      packer = new PixmapPacker(pageWidth, pageWidth, Format.RGBA8888, 2, false);
    }

    // to minimize collisions we'll use this format : pathWithoutExtension_size[_flip]_glyph
    String packPrefix = ownsAtlas ? "" : (filePath + '_' + parameter.size + (parameter.flip ? "_flip_" : '_'));

    for (int i = 0; i < parameter.characters.length(); i++) {
      char c = parameter.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);
      Glyph glyph = new Glyph();
      glyph.id = (int)c;
      glyph.width = pixmap.getWidth();
      glyph.height = pixmap.getHeight();
      glyph.xoffset = slot.getBitmapLeft();
      glyph.yoffset = parameter.flip ? -slot.getBitmapTop() + (int)baseLine : -(glyph.height - slot.getBitmapTop())
        - (int)baseLine;
      glyph.xadvance = FreeType.toInt(metrics.getHoriAdvance());

      if (bitmapped) {
        pixmap.setColor(Color.CLEAR);
        pixmap.fill();
        ByteBuffer buf = bitmap.getBuffer();
        for (int h = 0; h < glyph.height; h++) {
          int idx = h * bitmap.getPitch();
          for (int w = 0; w < (glyph.width + glyph.xoffset); w++) {
            int bit = (buf.get(idx + (w / 8)) >>> (7 - (w % 8))) & 1;
            pixmap.drawPixel(w, h, ((bit == 1) ? Color.WHITE.toIntBits() : Color.CLEAR.toIntBits()));
          }
        }

      }

      String name = packPrefix + c;
      Rectangle rect = packer.pack(name, pixmap);

      // determine which page it was packed into
      int pIndex = packer.getPageIndex(name);
      if (pIndex == -1) // we should not get here
        throw new IllegalStateException("packer was not able to insert '" + name + "' into a page");

      glyph.page = pIndex;
      glyph.srcX = (int)rect.x;
      glyph.srcY = (int)rect.y;

      data.setGlyph(c, glyph);
      pixmap.dispose();
    }

    // generate kerning
    for (int i = 0; i < parameter.characters.length(); i++) {
      for (int j = 0; j < parameter.characters.length(); j++) {
        char firstChar = parameter.characters.charAt(i);
        Glyph first = data.getGlyph(firstChar);
        if (first == null) continue;
        char secondChar = parameter.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) {
      Array<Page> pages = packer.getPages();
      data.regions = new TextureRegion[pages.size];

      for (int i = 0; i < pages.size; i++) {
        Page p = pages.get(i);

        Texture tex = new Texture(new PixmapTextureData(p.getPixmap(), p.getPixmap().getFormat(), parameter.genMipMaps,
          false, true)) {
          @Override
          public void dispose () {
            super.dispose();
            getTextureData().consumePixmap().dispose();
          }
        };
        tex.setFilter(parameter.minFilter, parameter.magFilter);

        data.regions[i] = new TextureRegion(tex);
      }
    }
    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 #getImagePaths()} and {@link #getFontFile()} methods will return null.
   * @author mzechner */
  public static class FreeTypeBitmapFontData extends BitmapFontData {
    TextureRegion[] regions;

    /** Returns the first texture region. Use getTextureRegions() instead
     * @return the first texture region in the array
     * @deprecated use getTextureRegions() instead */
    @Deprecated
    public TextureRegion getTextureRegion () {
      return regions[0];
    }

    public TextureRegion[] getTextureRegions () {
      return regions;
    }
  }

  /** 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 */
  public static class FreeTypeFontParameter {
    /** The size in pixels */
    public int size = 16;
    /** The characters the font should contain */
    public String characters = DEFAULT_CHARS;
    /** The optional PixmapPacker to use */
    public PixmapPacker packer = null;
    /** Whether to flip the font horizontally */
    public boolean flip = false;
    /** Whether or not 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;
  }
}
TOP

Related Classes of com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.