package com.arretadogames.pilot.render.opengl; import java.nio.IntBuffer; import javax.microedition.khronos.opengles.GL10; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.opengl.GLES11; import android.opengl.GLUtils; import android.util.SparseArray; import android.util.SparseIntArray; import com.arretadogames.pilot.loading.FontSpecification; public class GLTexturedFont { // Font texture for drawing text // General private FontSpecification fontSpec; // Render parameters private int fontHeight; private SparseIntArray charWidths; private SparseArray<Rect> characterRects; private int cellWidth; private int cellHeight; // Font settings (defaults) private int charStart = 32; private int charEnd = 126; private int charUnknown = 32; // Must be between start and end private int padX = 4; // X Padding 4 private int padY = 2; // Y Padding 4 private GLTexture spriteData; public GLTexturedFont(FontSpecification fontSpecification, GL10 gl) {//Typeface tf, GL10 gl) { this.fontSpec = fontSpecification; createTexture(gl); } private int getCharWidth(char c, float scale) { int charWidth = charWidths.get((int) c); charWidth -= 5; if (c == ' ') charWidth /= 2; return (int) (charWidth * scale); } public void drawText(GL10 gl, String text, int x, int y, float scale, boolean centered) { scale /= 2; // Get width of text int textWidth = 0; int charWidth; for (int i = 0; i < text.length(); i++) { textWidth += getCharWidth(text.charAt(i), scale); } if (centered) { // Adjust to centre text about x,y x -= textWidth / 2; } // Uncomment the following line to draw the baseline // GLLine.drawLineStrip(new Vec2[] { new Vec2(x, y), new Vec2(x + textWidth, y) }, 2, 2, Color.YELLOW, false, 1); y -= scale * fontHeight / 2; // No cycle through and draw text int scaledCellWidth = (int) (scale * cellWidth); int scaledCellHeight = (int) (scale * cellHeight); Rect src; Rect dst = new Rect(); int charNumber; gl.glPushMatrix(); for (int i = 0; i < text.length() ; i++) { // Source rect charNumber = (int) text.charAt(i); src = characterRects.get(charNumber); charWidth = charWidths.get(charNumber); charWidth = getCharWidth(text.charAt(i), scale); if (src == null) { // Defaults to unknown char src = characterRects.get(text.charAt(0)); charWidth = charWidths.get(text.charAt(0)); } // add less space between letters src = new Rect(src); src.right -= 1; // Adjust // Destination rect dst.set(x, y, x + scaledCellWidth, y + scaledCellHeight); // Draw GLTexturedRect.draw(gl, src, dst, spriteData); // Move forward CHAR WIDTH (not cell width) x += charWidth; } gl.glPopMatrix(); } private void createTexture(GL10 gl) { loadBitmap(gl, createBitmap()); } int textureSize; public Bitmap createBitmap() { // We use the font to create a sprite atlas containing every letter, // then we return the sprite atlas bitmap to be used as the texture. Paint paint = fontSpec.getFontPaint(); Paint mStrokePaint = fontSpec.getStrokePaint(); boolean hasStroke = mStrokePaint != null; // get font metrics Paint.FontMetrics fm = paint.getFontMetrics(); fontHeight = (int) Math.ceil(Math.abs(fm.bottom) + Math.abs(fm.top)); if (hasStroke) fontHeight += (int)Math.floor(mStrokePaint.getStrokeWidth()) * 2.3f; // Store for char widths charWidths = new SparseIntArray(); // Cycle through chars and store width of each character char[] s = new char[1]; float[] w = new float[1]; int charWidthMax = 0; int charWidth; for (char c = (char) charStart; c <= (char) charEnd; c++) { s[0] = c; paint.getTextWidths(s, 0, 1, w); charWidth = (int) Math.ceil(w[0]); if (hasStroke) charWidth += (int)Math.floor(mStrokePaint.getStrokeWidth()) * 2; // Store it charWidths.put(c, charWidth); if (charWidth > charWidthMax) { // Store max width charWidthMax = charWidth; } } // set character height to font height int charHeight = fontHeight; // Find the maximum size, validate, and setup cell sizes cellWidth = (int) charWidthMax + (2 * padX); cellHeight = (int) charHeight + (2 * padY); // Save whichever is bigger int maxSize = cellWidth > cellHeight ? cellWidth : cellHeight; // Ensure power-of-2 texture sizes. Base it on maxSize // Here is how calculation goes: // Number of chars = 95 // Square root (round up) = 10 x 10 grid // Max size = 256 / 10 = 25.6 (24?) textureSize = 0; if (maxSize <= 24) // IF Max Size is 24 or Less textureSize = 256; else if (maxSize <= 40) // ELSE IF Max Size is 40 or Less textureSize = 512; else if (maxSize <= 80) // ELSE IF Max Size is 80 or Less textureSize = 1024; else if (maxSize <= 160) // ELSE IF Max Size is 80 or Less textureSize = 2048; else // ELSE IF Max Size is Larger Than 80 (and Less than FONT_SIZE_MAX) textureSize = 4096; // Create an empty bitmap (alpha only) Bitmap bitmap = Bitmap.createBitmap(textureSize, textureSize, Bitmap.Config.ARGB_4444); // Create Canvas for rendering to Bitmap Canvas canvas = new Canvas(bitmap); bitmap.eraseColor(0x00000000); // Set Transparent Background (ARGB) // Render each of the characters to the canvas (i.e. build the font map) // Also store a source rectangle for each char characterRects = new SparseArray<Rect>(); int x = 0; int y = cellHeight;// (cellHeight - 1) - fontDescent - padY; for (char c = (char) charStart; c <= (char) charEnd; c++) { // Draw char s[0] = c; // Store source rectangle if (hasStroke) characterRects.put((int) c, new Rect( x - (int)(Math.floor(mStrokePaint.getStrokeWidth())), (int) (y - cellHeight * 0.7), x + cellWidth - padX * 4, (int) (y + cellHeight * 0.3 - padY))); else characterRects.put((int) c, new Rect(x, (int) (y - cellHeight * 0.7), x + cellWidth, (int) (y + cellHeight * 0.3))); canvas.drawText(s, 0, 1, x, y, paint); if (hasStroke) canvas.drawText(s, 0, 1, x, y, mStrokePaint); // Increment and wrap at end of line x += cellWidth; if ((x + cellWidth) > textureSize) { x = 0; y += cellHeight; } } return bitmap; } // Load Bitmap private void loadBitmap(GL10 gl, Bitmap bitmapToLoad) { IntBuffer t = IntBuffer.allocate(1); GLES11.glGenTextures(1, t); int texture_id = t.get(0); spriteData = new GLTexture(texture_id); // Working with textureId GLES11.glBindTexture(GL10.GL_TEXTURE_2D, texture_id); // SETTINGS // Scale up if the texture is smaller. GLES11.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); // Scale down if the mesh is smaller. GLES11.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR); // Clamp to edge behaviour at edge of texture (repeats last pixel) GLES11.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE); GLES11.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE); // Attach bitmap to current texture GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmapToLoad, 0); // Add dimensional info to spritedata spriteData.setDimensions(textureSize, textureSize); } public int getGLId() { return spriteData.getTextureID(); } }