public class

Font

extends Object
implements IFont
package org.andengine.opengl.font;

import java.util.ArrayList;

import org.andengine.opengl.font.exception.FontException;
import org.andengine.opengl.texture.ITexture;
import org.andengine.opengl.texture.PixelFormat;
import org.andengine.opengl.util.GLState;
import org.andengine.util.color.Color;
import org.andengine.util.math.MathUtils;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetrics;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.util.SparseArray;

/**
 * (c) 2010 Nicolas Gramlich
 * (c) 2011 Zynga Inc.
 * 
 * @author Nicolas Gramlich
 * @since 10:39:33 - 03.04.2010
 */
public class Font implements IFont {
	// ===========================================================
	// Constants
	// ===========================================================

	protected static final int LETTER_TEXTURE_PADDING = 1;

	// ===========================================================
	// Fields
	// ===========================================================

	private final FontManager mFontManager;

	private final ITexture mTexture;
	private final int mTextureWidth;
	private final int mTextureHeight;
	private int mCurrentTextureX = Font.LETTER_TEXTURE_PADDING;
	private int mCurrentTextureY = Font.LETTER_TEXTURE_PADDING;
	private int mCurrentTextureYHeightMax;

	private final SparseArray<Letter> mManagedCharacterToLetterMap = new SparseArray<Letter>();
	private final ArrayList<Letter> mLettersPendingToBeDrawnToTexture = new ArrayList<Letter>();

	protected final Paint mPaint;
	private final Paint mBackgroundPaint;

	protected final FontMetrics mFontMetrics;

	protected final Canvas mCanvas = new Canvas();
	protected final Rect mTextBounds = new Rect();
	protected final float[] mTextWidthContainer = new float[1];

	// ===========================================================
	// Constructors
	// ===========================================================

	public Font(final FontManager pFontManager, final ITexture pTexture, final Typeface pTypeface, final float pSize, final boolean pAntiAlias, final Color pColor) {
		this(pFontManager, pTexture, pTypeface, pSize, pAntiAlias, pColor.getARGBPackedInt());
	}

	public Font(final FontManager pFontManager, final ITexture pTexture, final Typeface pTypeface, final float pSize, final boolean pAntiAlias, final int pColorARGBPackedInt) {
		this.mFontManager = pFontManager;
		this.mTexture = pTexture;
		this.mTextureWidth = pTexture.getWidth();
		this.mTextureHeight = pTexture.getHeight();

		this.mBackgroundPaint = new Paint();
		this.mBackgroundPaint.setColor(Color.TRANSPARENT_ARGB_PACKED_INT);
		this.mBackgroundPaint.setStyle(Style.FILL);

		this.mPaint = new Paint();
		this.mPaint.setTypeface(pTypeface);
		this.mPaint.setColor(pColorARGBPackedInt);
		this.mPaint.setTextSize(pSize);
		this.mPaint.setAntiAlias(pAntiAlias);

		this.mFontMetrics = this.mPaint.getFontMetrics();
	}

	// ===========================================================
	// Getter & Setter
	// ===========================================================

	/**
	 * @return the gap between the lines.
	 */
	public float getLeading() {
		return this.mFontMetrics.leading;
	}

	/**
	 * @return the distance from the baseline to the top, which is usually negative.
	 */
	public float getAscent() {
		return this.mFontMetrics.ascent;
	}

	/**
	 * @return the distance from the baseline to the bottom, which is usually positive.
	 */
	public float getDescent() {
		return this.mFontMetrics.descent;
	}

	// ===========================================================
	// Methods for/from SuperClass/Interfaces
	// ===========================================================

	@Override
	public ITexture getTexture() {
		return this.mTexture;
	}

	@Override
	public void load() {
		this.mTexture.load();
		this.mFontManager.loadFont(this);
	}

	@Override
	public void unload() {
		this.mTexture.unload();
		this.mFontManager.unloadFont(this);
	}

	@Override
	public float getLineHeight() {
		return -this.getAscent() + this.getDescent();
	}

	@Override
	public synchronized Letter getLetter(final char pCharacter) throws FontException {
		Letter letter = this.mManagedCharacterToLetterMap.get(pCharacter);
		if(letter == null) {
			letter = this.createLetter(pCharacter);

			this.mLettersPendingToBeDrawnToTexture.add(letter);
			this.mManagedCharacterToLetterMap.put(pCharacter, letter);
		}
		return letter;
	}

	// ===========================================================
	// Methods
	// ===========================================================

	public synchronized void invalidateLetters() {
		final ArrayList<Letter> lettersPendingToBeDrawnToTexture = this.mLettersPendingToBeDrawnToTexture;
		final SparseArray<Letter> managedCharacterToLetterMap = this.mManagedCharacterToLetterMap;

		/* Make all letters redraw to the texture. */
		for(int i = managedCharacterToLetterMap.size() - 1; i >= 0; i--) {
			lettersPendingToBeDrawnToTexture.add(managedCharacterToLetterMap.valueAt(i));
		}
	}

	private float getLetterAdvance(final String pCharacterAsString) {
		this.mPaint.getTextWidths(pCharacterAsString, this.mTextWidthContainer);
		return this.mTextWidthContainer[0];
	}

	protected Bitmap getLetterBitmap(final Letter pLetter) throws FontException {
		final char character = pLetter.mCharacter;
		final String characterAsString = String.valueOf(character);

		final int width = pLetter.mWidth + (2 * Font.LETTER_TEXTURE_PADDING);
		final int height = pLetter.mHeight + (2 * Font.LETTER_TEXTURE_PADDING);

		final Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
		this.mCanvas.setBitmap(bitmap);

		/* Make background transparent. */
		this.mCanvas.drawRect(0, 0, bitmap.getWidth(), bitmap.getHeight(), this.mBackgroundPaint);

		/* Actually draw the character. */
		final float drawLetterLeft = -pLetter.mOffsetX;
		final float drawLetterTop = -(pLetter.mOffsetY + this.getAscent());

		this.drawLetter(characterAsString, drawLetterLeft, drawLetterTop);

		return bitmap;
	}

	protected void drawLetter(final String pCharacterAsString, final float pLeft, final float pTop) {
		this.mCanvas.drawText(pCharacterAsString, pLeft + Font.LETTER_TEXTURE_PADDING, pTop + Font.LETTER_TEXTURE_PADDING, this.mPaint);
	}

	public void prepareLetters(final char... pCharacters) throws FontException {
		for(final char character : pCharacters) {
			this.getLetter(character);
		}
	}

	private Letter createLetter(final char pCharacter) throws FontException {
		final String characterAsString = String.valueOf(pCharacter);

		final float textureWidth = this.mTextureWidth;
		final float textureHeight = this.mTextureHeight;

		this.updateTextBounds(characterAsString);
		final int letterLeft = this.mTextBounds.left;
		final int letterTop = this.mTextBounds.top;
		final int letterWidth = this.mTextBounds.width();
		final int letterHeight = this.mTextBounds.height();

		final Letter letter;

		final float advance = this.getLetterAdvance(characterAsString);

		final boolean whitespace = Character.isWhitespace(pCharacter) || (letterWidth == 0) || (letterHeight == 0);
		if(whitespace) {
			letter = new Letter(pCharacter, advance);
		} else {
			if((this.mCurrentTextureX + Font.LETTER_TEXTURE_PADDING + letterWidth) >= textureWidth) {
				this.mCurrentTextureX = 0;
				this.mCurrentTextureY += this.mCurrentTextureYHeightMax + (2 * Font.LETTER_TEXTURE_PADDING);
				this.mCurrentTextureYHeightMax = 0;
			}

			if((this.mCurrentTextureY + letterHeight) >= textureHeight) {
				throw new FontException("Not enough space for Letter: '" + pCharacter + "' on the Texture");
			}

			this.mCurrentTextureYHeightMax = Math.max(letterHeight, this.mCurrentTextureYHeightMax);

			this.mCurrentTextureX += Font.LETTER_TEXTURE_PADDING;

			final float u = this.mCurrentTextureX / textureWidth;
			final float v = this.mCurrentTextureY / textureHeight;
			final float u2 = (this.mCurrentTextureX + letterWidth) / textureWidth;
			final float v2 = (this.mCurrentTextureY + letterHeight) / textureHeight;

			letter = new Letter(pCharacter, this.mCurrentTextureX - Font.LETTER_TEXTURE_PADDING, this.mCurrentTextureY - Font.LETTER_TEXTURE_PADDING, letterWidth, letterHeight, letterLeft, letterTop - this.getAscent(), advance, u, v, u2, v2);
			this.mCurrentTextureX += letterWidth + Font.LETTER_TEXTURE_PADDING;
		}

		return letter;
	}

	protected void updateTextBounds(final String pCharacterAsString) {
		this.mPaint.getTextBounds(pCharacterAsString, 0, 1, this.mTextBounds);
	}

	public synchronized void update(final GLState pGLState) {
		if(this.mTexture.isLoadedToHardware()) {
			final ArrayList<Letter> lettersPendingToBeDrawnToTexture = this.mLettersPendingToBeDrawnToTexture;
			if(lettersPendingToBeDrawnToTexture.size() > 0) {
				this.mTexture.bind(pGLState);
				final PixelFormat pixelFormat = this.mTexture.getPixelFormat();

				final boolean preMultipyAlpha = this.mTexture.getTextureOptions().mPreMultiplyAlpha;
				for(int i = lettersPendingToBeDrawnToTexture.size() - 1; i >= 0; i--) {
					final Letter letter = lettersPendingToBeDrawnToTexture.get(i);
					if(!letter.isWhitespace()) {
						final Bitmap bitmap = this.getLetterBitmap(letter);

						final boolean useDefaultAlignment = MathUtils.isPowerOfTwo(bitmap.getWidth()) && MathUtils.isPowerOfTwo(bitmap.getHeight()) && (pixelFormat == PixelFormat.RGBA_8888);
						if(!useDefaultAlignment) {
							/* Adjust unpack alignment. */
							GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
						}

						if(preMultipyAlpha) {
							GLUtils.texSubImage2D(GLES20.GL_TEXTURE_2D, 0, letter.mTextureX, letter.mTextureY, bitmap);
						} else {
							pGLState.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, letter.mTextureX, letter.mTextureY, bitmap, pixelFormat);
						}

						if(!useDefaultAlignment) {
							/* Restore default unpack alignment. */
							GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, GLState.GL_UNPACK_ALIGNMENT_DEFAULT);
						}

						bitmap.recycle();
					}
				}
				lettersPendingToBeDrawnToTexture.clear();

				System.gc();
			}
		}
	}

	// ===========================================================
	// Inner and Anonymous Classes
	// ===========================================================
}