package org.anddev.andengine.opengl.font; import java.util.ArrayList; import javax.microedition.khronos.opengles.GL10; import org.anddev.andengine.opengl.texture.ITexture; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; 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.GLUtils; import android.util.FloatMath; 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 { // =========================================================== // Constants // =========================================================== protected static final float LETTER_LEFT_OFFSET = 0; protected static final int LETTER_EXTRA_WIDTH = 10; protected final static int PADDING = 1; // =========================================================== // Fields // =========================================================== private final ITexture mTexture; private final float mTextureWidth; private final float mTextureHeight; private int mCurrentTextureX = 0; private int mCurrentTextureY = 0; 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; private final int mLineHeight; private final int mLineGap; private final Size mCreateLetterTemporarySize = new Size(); private final Rect mGetLetterBitmapTemporaryRect = new Rect(); private final Rect mGetStringWidthTemporaryRect = new Rect(); private final Rect mGetLetterBoundsTemporaryRect = new Rect(); private final float[] mTemporaryTextWidthFetchers = new float[1]; protected final Canvas mCanvas = new Canvas(); // =========================================================== // Constructors // =========================================================== public Font(final ITexture pTexture, final Typeface pTypeface, final float pSize, final boolean pAntiAlias, final int pColor) { this.mTexture = pTexture; this.mTextureWidth = pTexture.getWidth(); this.mTextureHeight = pTexture.getHeight(); this.mPaint = new Paint(); this.mPaint.setTypeface(pTypeface); this.mPaint.setColor(pColor); this.mPaint.setTextSize(pSize); this.mPaint.setAntiAlias(pAntiAlias); this.mBackgroundPaint = new Paint(); this.mBackgroundPaint.setColor(Color.TRANSPARENT); this.mBackgroundPaint.setStyle(Style.FILL); this.mFontMetrics = this.mPaint.getFontMetrics(); this.mLineHeight = (int) FloatMath.ceil(Math.abs(this.mFontMetrics.ascent) + Math.abs(this.mFontMetrics.descent)) + (PADDING * 2); this.mLineGap = (int) (FloatMath.ceil(this.mFontMetrics.leading)); } // =========================================================== // Getter & Setter // =========================================================== public int getLineGap() { return this.mLineGap; } public int getLineHeight() { return this.mLineHeight; } public ITexture getTexture() { return this.mTexture; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== // =========================================================== // Methods // =========================================================== public synchronized void reload() { 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 int getLetterAdvance(final char pCharacter) { this.mPaint.getTextWidths(String.valueOf(pCharacter), this.mTemporaryTextWidthFetchers); return (int) (FloatMath.ceil(this.mTemporaryTextWidthFetchers[0])); } private Bitmap getLetterBitmap(final char pCharacter) { final Rect getLetterBitmapTemporaryRect = this.mGetLetterBitmapTemporaryRect; final String characterAsString = String.valueOf(pCharacter); this.mPaint.getTextBounds(characterAsString, 0, 1, getLetterBitmapTemporaryRect); getLetterBitmapTemporaryRect.right += PADDING * 2; final int lineHeight = this.getLineHeight(); final Bitmap bitmap = Bitmap.createBitmap(getLetterBitmapTemporaryRect.width() == 0 ? 1 + (2 * PADDING) : getLetterBitmapTemporaryRect.width() + LETTER_EXTRA_WIDTH, lineHeight, Bitmap.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. */ this.drawCharacterString(characterAsString); return bitmap; } protected void drawCharacterString(final String pCharacterAsString) { this.mCanvas.drawText(pCharacterAsString, LETTER_LEFT_OFFSET + PADDING, -this.mFontMetrics.ascent + PADDING, this.mPaint); } public int getStringWidth(final String pText) { this.mPaint.getTextBounds(pText, 0, pText.length(), this.mGetStringWidthTemporaryRect); return this.mGetStringWidthTemporaryRect.width(); } private void getLetterBounds(final char pCharacter, final Size pSize) { this.mPaint.getTextBounds(String.valueOf(pCharacter), 0, 1, this.mGetLetterBoundsTemporaryRect); pSize.set(this.mGetLetterBoundsTemporaryRect.width() + LETTER_EXTRA_WIDTH + (2 * PADDING), this.getLineHeight()); } public void prepareLetters(final char ... pCharacters) { for(final char character : pCharacters) { this.getLetter(character); } } public synchronized Letter getLetter(final char pCharacter) { final SparseArray<Letter> managedCharacterToLetterMap = this.mManagedCharacterToLetterMap; Letter letter = managedCharacterToLetterMap.get(pCharacter); if (letter == null) { letter = this.createLetter(pCharacter); this.mLettersPendingToBeDrawnToTexture.add(letter); managedCharacterToLetterMap.put(pCharacter, letter); } return letter; } private Letter createLetter(final char pCharacter) { final float textureWidth = this.mTextureWidth; final float textureHeight = this.mTextureHeight; final Size createLetterTemporarySize = this.mCreateLetterTemporarySize; this.getLetterBounds(pCharacter, createLetterTemporarySize); final float letterWidth = createLetterTemporarySize.getWidth(); final float letterHeight = createLetterTemporarySize.getHeight(); if (this.mCurrentTextureX + letterWidth >= textureWidth) { this.mCurrentTextureX = 0; this.mCurrentTextureY += this.getLineGap() + this.getLineHeight(); } final float letterTextureX = this.mCurrentTextureX / textureWidth; final float letterTextureY = this.mCurrentTextureY / textureHeight; final float letterTextureWidth = letterWidth / textureWidth; final float letterTextureHeight = letterHeight / textureHeight; final Letter letter = new Letter(pCharacter, this.getLetterAdvance(pCharacter), (int)letterWidth, (int)letterHeight, letterTextureX, letterTextureY, letterTextureWidth, letterTextureHeight); this.mCurrentTextureX += letterWidth; return letter; } public synchronized void update(final GL10 pGL) { final ArrayList<Letter> lettersPendingToBeDrawnToTexture = this.mLettersPendingToBeDrawnToTexture; if(lettersPendingToBeDrawnToTexture.size() > 0) { this.mTexture.bind(pGL); final float textureWidth = this.mTextureWidth; final float textureHeight = this.mTextureHeight; for(int i = lettersPendingToBeDrawnToTexture.size() - 1; i >= 0; i--) { final Letter letter = lettersPendingToBeDrawnToTexture.get(i); final Bitmap bitmap = this.getLetterBitmap(letter.mCharacter); // TODO What about premultiplyalpha of the textureOptions? GLUtils.texSubImage2D(GL10.GL_TEXTURE_2D, 0, (int)(letter.mTextureX * textureWidth), (int)(letter.mTextureY * textureHeight), bitmap); bitmap.recycle(); } lettersPendingToBeDrawnToTexture.clear(); System.gc(); } } // =========================================================== // Inner and Anonymous Classes // =========================================================== }