Package com.badlogic.gdx.graphics.g2d

Source Code of com.badlogic.gdx.graphics.g2d.BitmapFont$BitmapFontData

/*
* Copyright (c) 2008-2010, Matthias Mann
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
* conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Matthias Mann nor
* the names of its contributors may be used to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.badlogic.gdx.graphics.g2d;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.FloatArray;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.StreamUtils;

/** Renders bitmap fonts. The font consists of 2 files: an image file or {@link TextureRegion} containing the glyphs and a file in
* the AngleCode BMFont text format that describes where each glyph is on the image. Currently only a single image of glyphs is
* supported.<br>
* <br>
* Text is drawn using a {@link Batch}. Text can be cached in a {@link BitmapFontCache} for faster rendering of static text, which
* saves needing to compute the location of each glyph each frame.<br>
* <br>
* * The texture for a BitmapFont loaded from a file is managed. {@link #dispose()} must be called to free the texture when no
* longer needed. A BitmapFont loaded using a {@link TextureRegion} is managed if the region's texture is managed. Disposing the
* BitmapFont disposes the region's texture, which may not be desirable if the texture is still being used elsewhere.<br>
* <br>
* The code was originally based on Matthias Mann's TWL BitmapFont class. Thanks for sharing, Matthias! :)
* @author Nathan Sweet
* @author Matthias Mann */
public class BitmapFont implements Disposable {
  static private final int LOG2_PAGE_SIZE = 9;
  static private final int PAGE_SIZE = 1 << LOG2_PAGE_SIZE;
  static private final int PAGES = 0x10000 / PAGE_SIZE;

  public static final char[] xChars = {'x', 'e', 'a', 'o', 'n', 's', 'r', 'c', 'u', 'm', 'v', 'w', 'z'};
  public static final char[] capChars = {'M', 'N', 'B', 'D', 'C', 'E', 'F', 'K', 'A', 'G', 'H', 'I', 'J', 'L', 'O', 'P', 'Q',
    'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};

  final BitmapFontData data;
  TextureRegion[] regions;
  private final BitmapFontCache cache;
  private boolean flipped;
  private boolean integer;
  private boolean ownsTexture;
  boolean markupEnabled;

  /** Creates a BitmapFont using the default 15pt Arial font included in the libgdx JAR file. This is convenient to easily display
   * text without bothering with generating a bitmap font. */
  public BitmapFont () {
    this(Gdx.files.classpath("com/badlogic/gdx/utils/arial-15.fnt"),
      Gdx.files.classpath("com/badlogic/gdx/utils/arial-15.png"), false, true);
  }

  /** Creates a BitmapFont using the default 15pt Arial font included in the libgdx JAR file. This is convenient to easily display
   * text without bothering with generating a bitmap font.
   * @param flip If true, the glyphs will be flipped for use with a perspective where 0,0 is the upper left corner. */
  public BitmapFont (boolean flip) {
    this(Gdx.files.classpath("com/badlogic/gdx/utils/arial-15.fnt"),
      Gdx.files.classpath("com/badlogic/gdx/utils/arial-15.png"), flip, true);
  }

  /** Creates a BitmapFont with the glyphs relative to the specified region. If the region is null, the glyph textures are loaded
   * from the image file given in the font file. The {@link #dispose()} method will not dispose the region's texture in this
   * case!
   *
   * The font data is not flipped.
   *
   * @param fontFile the font definition file
   * @param region The texture region containing the glyphs. The glyphs must be relative to the lower left corner (ie, the region
   *           should not be flipped). If the region is null the glyph images are loaded from the image path in the font file. */
  public BitmapFont (FileHandle fontFile, TextureRegion region) {
    this(fontFile, region, false);
  }

  /** Creates a BitmapFont with the glyphs relative to the specified region. If the region is null, the glyph textures are loaded
   * from the image file given in the font file. The {@link #dispose()} method will not dispose the region's texture in this
   * case!
   * @param region The texture region containing the glyphs. The glyphs must be relative to the lower left corner (ie, the region
   *           should not be flipped). If the region is null the glyph images are loaded from the image path in the font file.
   * @param flip If true, the glyphs will be flipped for use with a perspective where 0,0 is the upper left corner. */
  public BitmapFont (FileHandle fontFile, TextureRegion region, boolean flip) {
    this(new BitmapFontData(fontFile, flip), region, true);
  }

  /** Creates a BitmapFont from a BMFont file. The image file name is read from the BMFont file and the image is loaded from the
   * same directory. The font data is not flipped. */
  public BitmapFont (FileHandle fontFile) {
    this(fontFile, false);
  }

  /** Creates a BitmapFont from a BMFont file. The image file name is read from the BMFont file and the image is loaded from the
   * same directory.
   * @param flip If true, the glyphs will be flipped for use with a perspective where 0,0 is the upper left corner. */
  public BitmapFont (FileHandle fontFile, boolean flip) {
    this(new BitmapFontData(fontFile, flip), (TextureRegion)null, true);
  }

  /** Creates a BitmapFont from a BMFont file, using the specified image for glyphs. Any image specified in the BMFont file is
   * ignored.
   * @param flip If true, the glyphs will be flipped for use with a perspective where 0,0 is the upper left corner. */
  public BitmapFont (FileHandle fontFile, FileHandle imageFile, boolean flip) {
    this(fontFile, imageFile, flip, true);
  }

  /** Creates a BitmapFont from a BMFont file, using the specified image for glyphs. Any image specified in the BMFont file is
   * ignored.
   * @param flip If true, the glyphs will be flipped for use with a perspective where 0,0 is the upper left corner.
   * @param integer If true, rendering positions will be at integer values to avoid filtering artifacts. */
  public BitmapFont (FileHandle fontFile, FileHandle imageFile, boolean flip, boolean integer) {
    this(new BitmapFontData(fontFile, flip), new TextureRegion(new Texture(imageFile, false)), integer);
    ownsTexture = true;
  }

  /** Constructs a new BitmapFont from the given {@link BitmapFontData} and {@link TextureRegion}. If the TextureRegion is null,
   * the image path(s) will be read from the BitmapFontData. The dispose() method will not dispose the texture of the region(s)
   * if the region is != null.
   *
   * Passing a single TextureRegion assumes that your font only needs a single texture page. If you need to support multiple
   * pages, either let the Font read the images themselves (by specifying null as the TextureRegion), or by specifying each page
   * manually with the TextureRegion[] constructor.
   *
   * @param integer If true, rendering positions will be at integer values to avoid filtering artifacts. */
  public BitmapFont (BitmapFontData data, TextureRegion region, boolean integer) {
    this(data, region != null ? new TextureRegion[] {region} : null, integer);
  }

  /** Constructs a new BitmapFont from the given {@link BitmapFontData} and array of {@link TextureRegion}. If the TextureRegion
   * is null or empty, the image path(s) will be read from the BitmapFontData. The dispose() method will not dispose the texture
   * of the region(s) if the regions array is != null and not empty.
   *
   * @param integer If true, rendering positions will be at integer values to avoid filtering artifacts. */
  public BitmapFont (BitmapFontData data, TextureRegion[] regions, boolean integer) {
    if (regions == null || regions.length == 0) {
      // load each path
      this.regions = new TextureRegion[data.imagePaths.length];
      for (int i = 0; i < this.regions.length; i++) {
        if (data.fontFile == null) {
          this.regions[i] = new TextureRegion(new Texture(Gdx.files.internal(data.imagePaths[i]), false));
        } else {
          this.regions[i] = new TextureRegion(new Texture(Gdx.files.getFileHandle(data.imagePaths[i], data.fontFile.type()),
            false));
        }
      }
      ownsTexture = true;
    } else {
      this.regions = regions;
      ownsTexture = false;
    }

    cache = new BitmapFontCache(this);
    cache.setUseIntegerPositions(integer);

    this.flipped = data.flipped;
    this.data = data;
    this.integer = integer;
    load(data);
  }

  private void load (BitmapFontData data) {
    for (Glyph[] page : data.glyphs) {
      if (page == null) continue;
      for (Glyph glyph : page) {
        if (glyph == null) continue;

        TextureRegion region = regions[glyph.page];

        if (region == null) {
          // TODO: support null regions by parsing scaleW / scaleH ?
          throw new IllegalArgumentException("BitmapFont texture region array cannot contain null elements");
        }

        float invTexWidth = 1.0f / region.getTexture().getWidth();
        float invTexHeight = 1.0f / region.getTexture().getHeight();

        float offsetX = 0, offsetY = 0;
        float u = region.u;
        float v = region.v;
        float regionWidth = region.getRegionWidth();
        float regionHeight = region.getRegionHeight();
        if (region instanceof AtlasRegion) {
          // Compensate for whitespace stripped from left and top edges.
          AtlasRegion atlasRegion = (AtlasRegion)region;
          offsetX = atlasRegion.offsetX;
          offsetY = atlasRegion.originalHeight - atlasRegion.packedHeight - atlasRegion.offsetY;
        }

        float x = glyph.srcX;
        float x2 = glyph.srcX + glyph.width;
        float y = glyph.srcY;
        float y2 = glyph.srcY + glyph.height;

        // Shift glyph for left and top edge stripped whitespace. Clip glyph for right and bottom edge stripped whitespace.
        if (offsetX > 0) {
          x -= offsetX;
          if (x < 0) {
            glyph.width += x;
            glyph.xoffset -= x;
            x = 0;
          }
          x2 -= offsetX;
          if (x2 > regionWidth) {
            glyph.width -= x2 - regionWidth;
            x2 = regionWidth;
          }
        }
        if (offsetY > 0) {
          y -= offsetY;
          if (y < 0) {
            glyph.height += y;
            y = 0;
          }
          y2 -= offsetY;
          if (y2 > regionHeight) {
            float amount = y2 - regionHeight;
            glyph.height -= amount;
            glyph.yoffset += amount;
            y2 = regionHeight;
          }
        }

        glyph.u = u + x * invTexWidth;
        glyph.u2 = u + x2 * invTexWidth;
        if (data.flipped) {
          glyph.v = v + y * invTexHeight;
          glyph.v2 = v + y2 * invTexHeight;
        } else {
          glyph.v2 = v + y * invTexHeight;
          glyph.v = v + y2 * invTexHeight;
        }
      }
    }
  }

  /** Draws a string at the specified position.
   * @see BitmapFontCache#addText(CharSequence, float, float, int, int) */
  public TextBounds draw (Batch batch, CharSequence str, float x, float y) {
    cache.clear();
    TextBounds bounds = cache.addText(str, x, y, 0, str.length());
    cache.draw(batch);
    return bounds;
  }

  /** Draws a string at the specified position.
   * @see BitmapFontCache#addText(CharSequence, float, float, int, int) */
  public TextBounds draw (Batch batch, CharSequence str, float x, float y, int start, int end) {
    cache.clear();
    TextBounds bounds = cache.addText(str, x, y, start, end);
    cache.draw(batch);
    return bounds;
  }

  /** Draws a string, which may contain newlines (\n), at the specified position.
   * @see BitmapFontCache#addMultiLineText(CharSequence, float, float, float, HAlignment) */
  public TextBounds drawMultiLine (Batch batch, CharSequence str, float x, float y) {
    cache.clear();
    TextBounds bounds = cache.addMultiLineText(str, x, y, 0, HAlignment.LEFT);
    cache.draw(batch);
    return bounds;
  }

  /** Draws a string, which may contain newlines (\n), at the specified position.
   * @see BitmapFontCache#addMultiLineText(CharSequence, float, float, float, HAlignment) */
  public TextBounds drawMultiLine (Batch batch, CharSequence str, float x, float y, float alignmentWidth, HAlignment alignment) {
    cache.clear();
    TextBounds bounds = cache.addMultiLineText(str, x, y, alignmentWidth, alignment);
    cache.draw(batch);
    return bounds;
  }

  /** Draws a string, which may contain newlines (\n), with the specified position. Each line is automatically wrapped within the
   * specified width.
   * @see BitmapFontCache#addWrappedText(CharSequence, float, float, float, HAlignment) */
  public TextBounds drawWrapped (Batch batch, CharSequence str, float x, float y, float wrapWidth) {
    cache.clear();
    TextBounds bounds = cache.addWrappedText(str, x, y, wrapWidth, HAlignment.LEFT);
    cache.draw(batch);
    return bounds;
  }

  /** Draws a string, which may contain newlines (\n), with the specified position. Each line is automatically wrapped within the
   * specified width.
   * @see BitmapFontCache#addWrappedText(CharSequence, float, float, float, HAlignment) */
  public TextBounds drawWrapped (Batch batch, CharSequence str, float x, float y, float wrapWidth, HAlignment alignment) {
    cache.clear();
    TextBounds bounds = cache.addWrappedText(str, x, y, wrapWidth, alignment);
    cache.draw(batch);
    return bounds;
  }

  /** Returns the bounds of the specified text. Note the returned TextBounds instance is reused.
   * @see #getBounds(CharSequence, int, int, TextBounds) */
  public TextBounds getBounds (CharSequence str) {
    return getBounds(str, 0, str.length(), cache.getBounds());
  }

  /** Returns the bounds of the specified text.
   * @see #getBounds(CharSequence, int, int, TextBounds) */
  public TextBounds getBounds (CharSequence str, TextBounds textBounds) {
    return getBounds(str, 0, str.length(), textBounds);
  }

  /** Returns the bounds of the specified text. Note the returned TextBounds instance is reused.
   * @see #getBounds(CharSequence, int, int, TextBounds) */
  public TextBounds getBounds (CharSequence str, int start, int end) {
    return getBounds(str, start, end, cache.getBounds());
  }

  /** Returns the size of the specified string. The height is the distance from the top of most capital letters in the font (the
   * {@link #getCapHeight() cap height}) to the baseline.
   * @param start The first character of the string.
   * @param end The last character of the string (exclusive). */
  public TextBounds getBounds (CharSequence str, int start, int end, TextBounds textBounds) {
    BitmapFontData data = this.data;
    int width = 0;
    Glyph lastGlyph = null;
    while (start < end) {
      char ch = str.charAt(start++);
      if (ch == '[' && markupEnabled) {
        if (!(start < end && str.charAt(start) == '[')) { // non escaped '['
          while (start < end && str.charAt(start) != ']')
            start++;
          start++;
          continue;
        }
        start++;
      }
      lastGlyph = data.getGlyph(ch);
      if (lastGlyph != null) {
        width = lastGlyph.xadvance;
        break;
      }
    }
    while (start < end) {
      char ch = str.charAt(start++);
      if (ch == '[' && markupEnabled) {
        if (!(start < end && str.charAt(start) == '[')) { // non escaped '['
          while (start < end && str.charAt(start) != ']')
            start++;
          start++;
          continue;
        }
        start++;
      }
      Glyph g = data.getGlyph(ch);
      if (g != null) {
        width += lastGlyph.getKerning(ch);
        lastGlyph = g;
        width += g.xadvance;
      }
    }
    textBounds.width = width * data.scaleX;
    textBounds.height = data.capHeight;
    return textBounds;
  }

  /** Returns the bounds of the specified text, which may contain newlines.
   * @see #getMultiLineBounds(CharSequence, TextBounds) */
  public TextBounds getMultiLineBounds (CharSequence str) {
    return getMultiLineBounds(str, cache.getBounds());
  }

  /** Returns the bounds of the specified text, which may contain newlines. The height is the distance from the top of most
   * capital letters in the font (the {@link #getCapHeight() cap height}) to the baseline of the last line of text. */
  public TextBounds getMultiLineBounds (CharSequence str, TextBounds textBounds) {
    int start = 0;
    float maxWidth = 0;
    int numLines = 0;
    int length = str.length();
    while (start < length) {
      int lineEnd = indexOf(str, '\n', start);
      float lineWidth = getBounds(str, start, lineEnd).width;
      maxWidth = Math.max(maxWidth, lineWidth);
      start = lineEnd + 1;
      numLines++;
    }
    textBounds.width = maxWidth;
    textBounds.height = data.capHeight + (numLines - 1) * data.lineHeight;
    return textBounds;
  }

  /** Returns the bounds of the specified text, which may contain newlines and is wrapped within the specified width.
   * @see #getWrappedBounds(CharSequence, float, TextBounds) */
  public TextBounds getWrappedBounds (CharSequence str, float wrapWidth) {
    return getWrappedBounds(str, wrapWidth, cache.getBounds());
  }

  /** Returns the bounds of the specified text, which may contain newlines and is wrapped within the specified width. The height
   * is the distance from the top of most capital letters in the font (the {@link #getCapHeight() cap height}) to the baseline of
   * the last line of text.
   * @param wrapWidth Width to wrap the bounds within. */
  public TextBounds getWrappedBounds (CharSequence str, float wrapWidth, TextBounds textBounds) {
    if (wrapWidth <= 0) wrapWidth = Integer.MAX_VALUE;
    int start = 0;
    int numLines = 0;
    int length = str.length();
    float maxWidth = 0;
    while (start < length) {
      int newLine = BitmapFont.indexOf(str, '\n', start);
      // Eat whitespace at start of line.
      while (start < newLine) {
        if (!BitmapFont.isWhitespace(str.charAt(start))) break;
        start++;
      }
      int lineEnd = start + computeVisibleGlyphs(str, start, newLine, wrapWidth);
      int nextStart = lineEnd + 1;
      if (lineEnd < newLine) {
        // Find char to break on.
        while (lineEnd > start) {
          if (BitmapFont.isWhitespace(str.charAt(lineEnd))) break;
          lineEnd--;
        }
        if (lineEnd == start) {
          if (nextStart > start + 1) nextStart--;
          lineEnd = nextStart; // If no characters to break, show all.
        } else {
          nextStart = lineEnd;
          // Eat whitespace at end of line.
          while (lineEnd > start) {
            if (!BitmapFont.isWhitespace(str.charAt(lineEnd - 1))) break;
            lineEnd--;
          }
        }
      }
      if (lineEnd > start) {
        float lineWidth = getBounds(str, start, lineEnd).width;
        maxWidth = Math.max(maxWidth, lineWidth);
      }
      start = nextStart;
      numLines++;
    }
    textBounds.width = maxWidth;
    textBounds.height = data.capHeight + (numLines - 1) * data.lineHeight;
    return textBounds;
  }

  /** Computes the glyph advances for the given character sequence and stores them in the provided {@link FloatArray}. The float
   * arrays are cleared. An additional element is added at the end.
   * @param glyphAdvances the glyph advances output array.
   * @param glyphPositions the glyph positions output array. */
  public void computeGlyphAdvancesAndPositions (CharSequence str, FloatArray glyphAdvances, FloatArray glyphPositions) {
    glyphAdvances.clear();
    glyphPositions.clear();
    int index = 0;
    int end = str.length();
    float width = 0;
    Glyph lastGlyph = null;
    BitmapFontData data = this.data;
    if (data.scaleX == 1) {
      for (; index < end; index++) {
        char ch = str.charAt(index);
        Glyph g = data.getGlyph(ch);
        if (g != null) {
          if (lastGlyph != null) width += lastGlyph.getKerning(ch);
          lastGlyph = g;
          glyphAdvances.add(g.xadvance);
          glyphPositions.add(width);
          width += g.xadvance;
        }
      }
      glyphAdvances.add(0);
      glyphPositions.add(width);
    } else {
      float scaleX = this.data.scaleX;
      for (; index < end; index++) {
        char ch = str.charAt(index);
        Glyph g = data.getGlyph(ch);
        if (g != null) {
          if (lastGlyph != null) width += lastGlyph.getKerning(ch) * scaleX;
          lastGlyph = g;
          float xadvance = g.xadvance * scaleX;
          glyphAdvances.add(xadvance);
          glyphPositions.add(width);
          width += xadvance;
        }
      }
      glyphAdvances.add(0);
      glyphPositions.add(width);
    }
  }

  /** Returns the number of glyphs from the substring that can be rendered in the specified width.
   * @param start The first character of the string.
   * @param end The last character of the string (exclusive). */
  public int computeVisibleGlyphs (CharSequence str, int start, int end, float availableWidth) {
    BitmapFontData data = this.data;
    int index = start;
    float width = 0;
    Glyph lastGlyph = null;
    availableWidth /= data.scaleX;

    for (; index < end; index++) {
      char ch = str.charAt(index);
      if (ch == '[' && markupEnabled) {
        index++;
        if (!(index < end && str.charAt(index) == '[')) { // non escaped '['
          while (index < end && str.charAt(index) != ']')
            index++;
          continue;
        }
      }
      Glyph g = data.getGlyph(ch);
      if (g != null) {
        if (lastGlyph != null) width += lastGlyph.getKerning(ch);
        if ((width + g.xadvance) - availableWidth > 0.001f) break;
        width += g.xadvance;
        lastGlyph = g;
      }
    }
    return index - start;
  }

  public void setColor (float color) {
    cache.setColor(color);
  }

  public void setColor (Color color) {
    cache.setColor(color);
  }

  public void setColor (float r, float g, float b, float a) {
    cache.setColor(r, g, b, a);
  }

  /** Returns the color of this font. Changing the returned color will have no affect, {@link #setColor(Color)} or
   * {@link #setColor(float, float, float, float)} must be used. */
  public Color getColor () {
    return cache.getColor();
  }

  /** Scales the font by the specified amounts on both axes <br>
   * <br>
   * Note that smoother scaling can be achieved if the texture backing the BitmapFont is using {@link TextureFilter#Linear}. The
   * default is Nearest, so use a BitmapFont constructor that takes a {@link TextureRegion}.
   *
   * @throws IllegalArgumentException When scaleXY is zero */
  public void setScale (float scaleX, float scaleY) {
    if (scaleX == 0 || scaleY == 0) {
      throw new IllegalArgumentException("Scale must not be zero");
    }
    BitmapFontData data = this.data;
    float x = scaleX / data.scaleX;
    float y = scaleY / data.scaleY;
    data.lineHeight = data.lineHeight * y;
    data.spaceWidth = data.spaceWidth * x;
    data.xHeight = data.xHeight * y;
    data.capHeight = data.capHeight * y;
    data.ascent = data.ascent * y;
    data.descent = data.descent * y;
    data.down = data.down * y;
    data.scaleX = scaleX;
    data.scaleY = scaleY;
  }

  /** Scales the font by the specified amount in both directions.
   * @see #setScale(float, float)
   * @throws IllegalArgumentException When scaleXY is zero */
  public void setScale (float scaleXY) {
    setScale(scaleXY, scaleXY);
  }

  /** Sets the font's scale relative to the current scale.
   * @see #setScale(float, float)
   * @throws IllegalArgumentException When resulting scale is zero */
  public void scale (float amount) {
    setScale(data.scaleX + amount, data.scaleY + amount);
  }

  public float getScaleX () {
    return data.scaleX;
  }

  public float getScaleY () {
    return data.scaleY;
  }

  /** Returns the first texture region. This is included for backwards-compatibility, and for convenience since most fonts only
   * use one texture page. For multi-page fonts, use getRegions().
   * @return the first texture region */
  // TODO: deprecate?
  public TextureRegion getRegion () {
    return regions[0];
  }

  /** Returns the array of TextureRegions that represents each texture page of glyphs.
   * @return the array of texture regions; modifying it may produce undesirable results */
  public TextureRegion[] getRegions () {
    return regions;
  }

  /** Returns the texture page at the given index.
   * @return the texture page at the given index */
  public TextureRegion getRegion (int index) {
    return regions[index];
  }

  /** Returns the line height, which is the distance from one line of text to the next. */
  public float getLineHeight () {
    return data.lineHeight;
  }

  /** Returns the width of the space character. */
  public float getSpaceWidth () {
    return data.spaceWidth;
  }

  /** Returns the x-height, which is the distance from the top of most lowercase characters to the baseline. */
  public float getXHeight () {
    return data.xHeight;
  }

  /** Returns the cap height, which is the distance from the top of most uppercase characters to the baseline. Since the drawing
   * position is the cap height of the first line, the cap height can be used to get the location of the baseline. */
  public float getCapHeight () {
    return data.capHeight;
  }

  /** Returns the ascent, which is the distance from the cap height to the top of the tallest glyph. */
  public float getAscent () {
    return data.ascent;
  }

  /** Returns the descent, which is the distance from the bottom of the glyph that extends the lowest to the baseline. This number
   * is negative. */
  public float getDescent () {
    return data.descent;
  }

  /** Returns true if this BitmapFont has been flipped for use with a y-down coordinate system. */
  public boolean isFlipped () {
    return flipped;
  }

  /** Returns true if color markup is enabled for this BitmapFont */
  public boolean isMarkupEnabled () {
    return markupEnabled;
  }

  /** Sets color markup on/off for this BitmapFont */
  public void setMarkupEnabled (boolean markupEnabled) {
    this.markupEnabled = markupEnabled;
  }

  /** Disposes the texture used by this BitmapFont's region IF this BitmapFont created the texture. */
  public void dispose () {
    if (ownsTexture) {
      for (int i = 0; i < regions.length; i++)
        regions[i].getTexture().dispose();
    }
  }

  /** Makes the specified glyphs fixed width. This can be useful to make the numbers in a font fixed width. Eg, when horizontally
   * centering a score or loading percentage text, it will not jump around as different numbers are shown. */
  public void setFixedWidthGlyphs (CharSequence glyphs) {
    BitmapFontData data = this.data;
    int maxAdvance = 0;
    for (int index = 0, end = glyphs.length(); index < end; index++) {
      Glyph g = data.getGlyph(glyphs.charAt(index));
      if (g != null && g.xadvance > maxAdvance) maxAdvance = g.xadvance;
    }
    for (int index = 0, end = glyphs.length(); index < end; index++) {
      Glyph g = data.getGlyph(glyphs.charAt(index));
      if (g == null) continue;
      g.xoffset += (maxAdvance - g.xadvance) / 2;
      g.xadvance = maxAdvance;
      g.kerning = null;
    }
  }

  /** Checks whether this BitmapFont data contains a given character. */
  public boolean containsCharacter (char character) {
    return data.getGlyph(character) != null;
  }

  /** Specifies whether to use integer positions or not. Default is to use them so filtering doesn't kick in as badly. */
  public void setUseIntegerPositions (boolean integer) {
    this.integer = integer;
    cache.setUseIntegerPositions(integer);
  }

  /** Checks whether this font uses integer positions for drawing. */
  public boolean usesIntegerPositions () {
    return integer;
  }

  /** For expert usage -- returns the BitmapFontCache used by this font, for rendering to a sprite batch. This can be used, for
   * example, to manipulate glyph colors within a specific index.
   * @return the bitmap font cache used by this font */
  public BitmapFontCache getCache () {
    return cache;
  }

  /** Gets the underlying {@link BitmapFontData} for this BitmapFont. */
  public BitmapFontData getData () {
    return data;
  }

  /** @return whether the texture is owned by the font, font disposes the texture itself if true */
  public boolean ownsTexture () {
    return ownsTexture;
  }

  /** Sets whether the font owns the texture or not. In case it does, the font will also dispose of the texture when
   * {@link #dispose()} is called. Use with care!
   * @param ownsTexture whether the font owns the texture */
  public void setOwnsTexture (boolean ownsTexture) {
    this.ownsTexture = ownsTexture;
  }

  /** Represents a single character in a font page. */
  public static class Glyph {
    public int id;
    public int srcX;
    public int srcY;
    public int width, height;
    public float u, v, u2, v2;
    public int xoffset, yoffset;
    public int xadvance;
    public byte[][] kerning;

    /** The index to the texture page that holds this glyph. */
    public int page = 0;

    public int getKerning (char ch) {
      if (kerning != null) {
        byte[] page = kerning[ch >>> LOG2_PAGE_SIZE];
        if (page != null) return page[ch & PAGE_SIZE - 1];
      }
      return 0;
    }

    public void setKerning (int ch, int value) {
      if (kerning == null) kerning = new byte[PAGES][];
      byte[] page = kerning[ch >>> LOG2_PAGE_SIZE];
      if (page == null) kerning[ch >>> LOG2_PAGE_SIZE] = page = new byte[PAGE_SIZE];
      page[ch & PAGE_SIZE - 1] = (byte)value;
    }
  }

  static int indexOf (CharSequence text, char ch, int start) {
    final int n = text.length();
    for (; start < n; start++)
      if (text.charAt(start) == ch) return start;
    return n;
  }

  static boolean isWhitespace (char c) {
    switch (c) {
    case '\n':
    case '\r':
    case '\t':
    case ' ':
      return true;
    default:
      return false;
    }
  }

  /** Arbitrarily definable text boundary */
  static public class TextBounds {
    public float width;
    public float height;

    public TextBounds () {
    }

    public TextBounds (TextBounds bounds) {
      set(bounds);
    }

    public void set (TextBounds bounds) {
      width = bounds.width;
      height = bounds.height;
    }
  }

  /** Defines possible horizontal alignments. */
  static public enum HAlignment {
    LEFT, CENTER, RIGHT
  }

  /** Backing data for a {@link BitmapFont}. */
  public static class BitmapFontData {
    /** The first discovered image path; included for backwards-compatibility This is the same as imagePaths[0].
     *
     * @deprecated use imagePaths[0] instead */
    @Deprecated public String imagePath;

    /** An array of the image paths, i.e. for multiple texture pages */
    public String[] imagePaths;
    public FileHandle fontFile;
    public boolean flipped;
    public float lineHeight;
    public float capHeight = 1;
    public float ascent;
    public float descent;
    public float down;
    public float scaleX = 1, scaleY = 1;

    public final Glyph[][] glyphs = new Glyph[PAGES][];
    public float spaceWidth;
    public float xHeight = 1;

    /** Use this if you want to create BitmapFontData yourself, e.g. from stb-truetype of FreeType. */
    public BitmapFontData () {
    }

    @SuppressWarnings("deprecation")
    public BitmapFontData (FileHandle fontFile, boolean flip) {
      this.fontFile = fontFile;
      this.flipped = flip;
      BufferedReader reader = new BufferedReader(new InputStreamReader(fontFile.read()), 512);
      try {
        reader.readLine(); // info

        String line = reader.readLine();
        if (line == null) throw new GdxRuntimeException("Invalid font file: " + fontFile);
        String[] common = line.split(" ", 7); // we want the 6th element to be in tact; i.e. "page=N"

        // we only really NEED lineHeight and base
        if (common.length < 3) throw new GdxRuntimeException("Invalid font file: " + fontFile);

        if (!common[1].startsWith("lineHeight=")) throw new GdxRuntimeException("Invalid font file: " + fontFile);
        lineHeight = Integer.parseInt(common[1].substring(11));

        if (!common[2].startsWith("base=")) throw new GdxRuntimeException("Invalid font file: " + fontFile);
        float baseLine = Integer.parseInt(common[2].substring(5));

        // parse the pages count
        int imgPageCount = 1;
        if (common.length >= 6 && common[5] != null && common[5].startsWith("pages=")) {
          try {
            imgPageCount = Math.max(1, Integer.parseInt(common[5].substring(6)));
          } catch (NumberFormatException e) {
            // just ignore and only use one page...
            // somebody must have tampered with the page count >:(
          }
        }

        imagePaths = new String[imgPageCount];

        // read each page definition
        for (int p = 0; p < imgPageCount; p++) {
          // read each "page" info line
          line = reader.readLine();
          if (line == null) throw new GdxRuntimeException("Expected more 'page' definitions in font file " + fontFile);
          String[] pageLine = line.split(" ", 4);
          if (!pageLine[2].startsWith("file=")) throw new GdxRuntimeException("Invalid font file: " + fontFile);

          // we will expect ID to mean "index" -- if for some reason this is not the case, it will fuck everything up
          // so we need to warn the user that their BMFont output is bogus
          if (pageLine[1].startsWith("id=")) {
            try {
              int pageID = Integer.parseInt(pageLine[1].substring(3));
              if (pageID != p)
                throw new GdxRuntimeException("Invalid font file: " + fontFile
                  + " -- page ids must be indices starting at 0");
            } catch (NumberFormatException e) {
              throw new GdxRuntimeException("NumberFormatException on 'page id' element of " + fontFile);
            }
          }

          String imgFilename = null;
          if (pageLine[2].endsWith("\"")) {
            imgFilename = pageLine[2].substring(6, pageLine[2].length() - 1);
          } else {
            imgFilename = pageLine[2].substring(5, pageLine[2].length());
          }

          String path = fontFile.parent().child(imgFilename).path().replaceAll("\\\\", "/");
          if (this.imagePath == null) this.imagePath = path;
          imagePaths[p] = path;
        }
        descent = 0;

        while (true) {
          line = reader.readLine();
          if (line == null) break; // EOF
          if (line.startsWith("kernings ")) break; // Starting kernings block
          if (!line.startsWith("char ")) continue;

          Glyph glyph = new Glyph();

          StringTokenizer tokens = new StringTokenizer(line, " =");
          tokens.nextToken();
          tokens.nextToken();
          int ch = Integer.parseInt(tokens.nextToken());
          if (ch <= Character.MAX_VALUE)
            setGlyph(ch, glyph);
          else
            continue;
          glyph.id = ch;
          tokens.nextToken();
          glyph.srcX = Integer.parseInt(tokens.nextToken());
          tokens.nextToken();
          glyph.srcY = Integer.parseInt(tokens.nextToken());
          tokens.nextToken();
          glyph.width = Integer.parseInt(tokens.nextToken());
          tokens.nextToken();
          glyph.height = Integer.parseInt(tokens.nextToken());
          tokens.nextToken();
          glyph.xoffset = Integer.parseInt(tokens.nextToken());
          tokens.nextToken();
          if (flip)
            glyph.yoffset = Integer.parseInt(tokens.nextToken());
          else
            glyph.yoffset = -(glyph.height + Integer.parseInt(tokens.nextToken()));
          tokens.nextToken();
          glyph.xadvance = Integer.parseInt(tokens.nextToken());

          // also check for page.. a little safer here since we don't want to break any old functionality
          // and since maybe some shitty BMFont tools won't bother writing page id??
          if (tokens.hasMoreTokens()) tokens.nextToken();
          if (tokens.hasMoreTokens()) {
            try {
              glyph.page = Integer.parseInt(tokens.nextToken());
            } catch (NumberFormatException e) {
            }
          }

          if (glyph.width > 0 && glyph.height > 0) descent = Math.min(baseLine + glyph.yoffset, descent);
        }

        while (true) {
          line = reader.readLine();
          if (line == null) break;
          if (!line.startsWith("kerning ")) break;

          StringTokenizer tokens = new StringTokenizer(line, " =");
          tokens.nextToken();
          tokens.nextToken();
          int first = Integer.parseInt(tokens.nextToken());
          tokens.nextToken();
          int second = Integer.parseInt(tokens.nextToken());
          if (first < 0 || first > Character.MAX_VALUE || second < 0 || second > Character.MAX_VALUE) continue;
          Glyph glyph = getGlyph((char)first);
          tokens.nextToken();
          int amount = Integer.parseInt(tokens.nextToken());
          if (glyph != null) { // it appears BMFont outputs kerning for glyph pairs not contained in the font, hence the null
// check
            glyph.setKerning(second, amount);
          }
        }

        Glyph spaceGlyph = getGlyph(' ');
        if (spaceGlyph == null) {
          spaceGlyph = new Glyph();
          spaceGlyph.id = (int)' ';
          Glyph xadvanceGlyph = getGlyph('l');
          if (xadvanceGlyph == null) xadvanceGlyph = getFirstGlyph();
          spaceGlyph.xadvance = xadvanceGlyph.xadvance;
          setGlyph(' ', spaceGlyph);
        }
        spaceWidth = spaceGlyph != null ? spaceGlyph.xadvance + spaceGlyph.width : 1;

        Glyph xGlyph = null;
        for (int i = 0; i < xChars.length; i++) {
          xGlyph = getGlyph(xChars[i]);
          if (xGlyph != null) break;
        }
        if (xGlyph == null) xGlyph = getFirstGlyph();
        xHeight = xGlyph.height;

        Glyph capGlyph = null;
        for (int i = 0; i < capChars.length; i++) {
          capGlyph = getGlyph(capChars[i]);
          if (capGlyph != null) break;
        }
        if (capGlyph == null) {
          for (Glyph[] page : this.glyphs) {
            if (page == null) continue;
            for (Glyph glyph : page) {
              if (glyph == null || glyph.height == 0 || glyph.width == 0) continue;
              capHeight = Math.max(capHeight, glyph.height);
            }
          }
        } else
          capHeight = capGlyph.height;

        ascent = baseLine - capHeight;
        down = -lineHeight;
        if (flip) {
          ascent = -ascent;
          down = -down;
        }
      } catch (Exception ex) {
        throw new GdxRuntimeException("Error loading font file: " + fontFile, ex);
      } finally {
        StreamUtils.closeQuietly(reader);
      }
    }

    public void setGlyph (int ch, Glyph glyph) {
      Glyph[] page = glyphs[ch / PAGE_SIZE];
      if (page == null) glyphs[ch / PAGE_SIZE] = page = new Glyph[PAGE_SIZE];
      page[ch & PAGE_SIZE - 1] = glyph;
    }

    public Glyph getFirstGlyph () {
      for (Glyph[] page : this.glyphs) {
        if (page == null) continue;
        for (Glyph glyph : page) {
          if (glyph == null || glyph.height == 0 || glyph.width == 0) continue;
          return glyph;
        }
      }
      throw new GdxRuntimeException("No glyphs found!");
    }

    /** Returns the glyph for the specified character, or null if no such glyph exists. */
    public Glyph getGlyph (char ch) {
      Glyph[] page = glyphs[ch / PAGE_SIZE];
      if (page != null) return page[ch & PAGE_SIZE - 1];
      return null;
    }

    /** Returns the first image path; included for backwards-compatibility. Use getImagePath(int) instead.
     * @return the first image path in the array
     * @deprecated use getImagePath(int index) instead */
    @Deprecated
    public String getImagePath () {
      return imagePath;
    }

    /** Returns the image path for the texture page at the given index.
     * @param index the index of the page, AKA the "id" in the BMFont file
     * @return the texture page */
    public String getImagePath (int index) {
      return imagePaths[index];
    }

    public String[] getImagePaths () {
      return imagePaths;
    }

    public FileHandle getFontFile () {
      return fontFile;
    }
  }
}
TOP

Related Classes of com.badlogic.gdx.graphics.g2d.BitmapFont$BitmapFontData

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.