package com.glview.hwui.font; import java.nio.ByteBuffer; import java.util.Vector; import android.graphics.Color; import android.support.v4.util.LongSparseArray; import com.glview.font.FontUtils; import com.glview.freetype.FreeType; import com.glview.freetype.FreeType.Face; import com.glview.freetype.FreeType.SizeMetrics; import com.glview.graphics.Rect; import com.glview.graphics.Typeface; import com.glview.graphics.font.GlyphSlot; import com.glview.graphics.shader.A8TextureShader; import com.glview.hwui.GLCanvas; import com.glview.hwui.GLPaint; import com.glview.hwui.InnerGLCanvas; import com.glview.hwui.packer.PackerRect; import com.glview.internal.util.GrowingArrayUtils; import com.glview.libgdx.graphics.opengl.GL20; import com.glview.stackblur.BlurProcess; import com.glview.stackblur.NativeBlurProcess; import com.glview.text.TextUtils; public class FontRenderer { private final static int FONT_BORDER_SIZE = 1; FontRenderer() {} private final static FontRenderer sInstance = new FontRenderer(); public static FontRenderer instance() { return sInstance; } private boolean mInitialized; int mSmallCacheWidth = 1024; int mSmallCacheHeight = 512; int mLargeCacheWidth = 2048; int mLargeCacheHeight = 1024; byte[] mBuffer = new byte[2048]; class FontCaches { Vector<CacheTexture> mACacheTextures = new Vector<CacheTexture>(); LongSparseArray<FontRect> mCacheRects = new LongSparseArray<FontRect>(); } FontCaches mCacheTextures = new FontCaches(); FontCaches mShadowCacheTextures = new FontCaches(); LongSparseArray<FontData> mFontDatas = new LongSparseArray<FontData>(); GLCanvas mCanvas = null; BlurProcess mBlurProcess = new NativeBlurProcess(); public void release() { clearCacheTextures(mCacheTextures); clearCacheTextures(mShadowCacheTextures); mFontDatas.clear(); mInitialized = false; } // We don't want to allocate anything unless we actually draw text void checkInit() { if (mInitialized) { return; } initTextTexture(); mInitialized = true; } void initTextTexture() { clearCacheTextures(mCacheTextures); clearCacheTextures(mShadowCacheTextures); mCacheTextures.mACacheTextures.add(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, GL20.GL_ALPHA, true)); mCacheTextures.mACacheTextures.add(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, GL20.GL_ALPHA, false)); mCacheTextures.mACacheTextures.add(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, GL20.GL_ALPHA, false)); mCacheTextures.mACacheTextures.add(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, GL20.GL_ALPHA, false)); mShadowCacheTextures.mACacheTextures.add(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, GL20.GL_ALPHA, false)); mShadowCacheTextures.mACacheTextures.add(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, GL20.GL_ALPHA, false)); mShadowCacheTextures.mACacheTextures.add(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, GL20.GL_ALPHA, false)); } void clearCacheTextures(FontCaches caches) { for (int i = 0; i < caches.mACacheTextures.size(); i++) { caches.mACacheTextures.get(i).release(); } caches.mACacheTextures.clear(); caches.mCacheRects.clear(); } CacheTexture createCacheTexture(int width, int height, int format, boolean allocate) { CacheTexture cacheTexture = new CacheTexture(this, width, height, format); if (allocate) { cacheTexture.allocateTexture(); cacheTexture.allocateMesh(); } return cacheTexture; } public void flushBatch() { for (CacheTexture cacheTexture : mShadowCacheTextures.mACacheTextures) { if (cacheTexture.mFontBatch != null) { cacheTexture.mFontBatch.flush(); } } for (CacheTexture cacheTexture : mCacheTextures.mACacheTextures) { if (cacheTexture.mFontBatch != null) { cacheTexture.mFontBatch.flush(); } } } public void setGLCanvas(GLCanvas canvas) { mCanvas = canvas; } public GLCanvas getGLCanvas() { return mCanvas; } private final static boolean DEBUG_FONT_CACHE = false; private final static boolean DEBUG_FONT_SHADOW_CACHE = false; GLPaint mTestPaint = null, mTestPaint2 = null; public void end(InnerGLCanvas canvas) { if (DEBUG_FONT_CACHE) { if (mTestPaint == null) { mTestPaint = new GLPaint(); mTestPaint.setColor(Color.RED); mTestPaint.setShader(new A8TextureShader()); mTestPaint2 = new GLPaint(); mTestPaint2.setColor(Color.WHITE); } for (CacheTexture texture : mCacheTextures.mACacheTextures) { if (texture.mTexture.mId > 0) { ((GLCanvas) canvas).drawRect(0, 0, texture.mWidth, texture.mHeight, mTestPaint2); canvas.drawTexture(texture.mTexture, 0, 0, texture.mWidth, texture.mHeight, mTestPaint); } } } if (DEBUG_FONT_SHADOW_CACHE) { if (mTestPaint == null) { mTestPaint = new GLPaint(); mTestPaint.setColor(Color.RED); mTestPaint.setShader(new A8TextureShader()); mTestPaint2 = new GLPaint(); mTestPaint2.setColor(Color.WHITE); } for (CacheTexture texture : mShadowCacheTextures.mACacheTextures) { if (texture.mTexture.mId > 0) { ((GLCanvas) canvas).drawRect(0, 0, texture.mWidth, texture.mHeight, mTestPaint2); canvas.drawTexture(texture.mTexture, 0, 0, texture.mWidth, texture.mHeight, mTestPaint); } } } } public void renderText(GLCanvas canvas, CharSequence text, int start, int end, float x, float y, float alpha, GLPaint paint, Rect clip, float[] matrix, boolean forceFinish) { checkInit(); Typeface typeface = paint.getTypeface(); Face face = typeface.face(); int textSize = paint.getTextSize(); if (textSize < 5) return; int shadowRadius = (int) (paint.getShadowRadius() + 0.5f); int shadowColor = paint.getShadowColor(); boolean hasShadow = paint.hasShadow(); alpha = alpha * paint.getAlpha() / 255; synchronized (face) { face.setPixelSizes(0, textSize); long k = typeface.index() * 10000L + textSize; FontData fontData = mFontDatas.get(k); if (fontData == null) { fontData = new FontData(); SizeMetrics fontMetrics = face.getSize().getMetrics(); fontData.ascent = FreeType.toInt(fontMetrics.getAscender()); fontData.descent = FreeType.toInt(fontMetrics.getDescender()); fontData.lineHeight = FreeType.toInt(fontMetrics.getHeight()); if (face.loadChar(' ', FreeType.FT_LOAD_DEFAULT)) { fontData.spaceWidth = FreeType.toInt(face.getGlyph().getMetrics().getHoriAdvance()); } else { fontData.spaceWidth = FreeType.toInt(face.getMaxAdvanceWidth()); } } float baseline = y; for (int index = 0; index < end - start; index ++) { char c = text.charAt(index + start); if (TextUtils.isSpace(c)) { x += fontData.spaceWidth; continue; } int charIndex = face.getCharIndex(c); if (charIndex == 0) { c = 0; charIndex = face.getCharIndex(c); } if (charIndex == 0) continue; long key = charIndex * 10000000000L + typeface.index() * 10000000L + textSize * 10000L; FontRect r = mCacheTextures.mCacheRects.get(key); FontRect shadowR = null; if (hasShadow) { shadowR = mShadowCacheTextures.mCacheRects.get(key + shadowRadius); } if (r == null || (hasShadow && shadowR == null)) { if (!face.loadGlyph(charIndex, FreeType.FT_LOAD_DEFAULT)) { continue; } FreeType.GlyphSlot slot = face.getGlyph(); FreeType.Glyph glyph = slot.getGlyph(); try { glyph.toBitmap(FreeType.FT_RENDER_MODE_NORMAL); } catch (RuntimeException e) { glyph.dispose(); continue; } FreeType.Bitmap bitmap = glyph.getBitmap(); int w = bitmap.getWidth(); int h = bitmap.getRows(); if (w <= 0 || h <= 0) { continue; } if (r == null) { r = cacheBitmap(mCacheTextures, w, h, FONT_BORDER_SIZE, slot, glyph, bitmap, true); if (r != null) { mCacheTextures.mCacheRects.put(key, r); } } if (shadowR == null && hasShadow) { shadowR = cacheBitmapShadow(mShadowCacheTextures, w, h, shadowRadius, slot, glyph, bitmap, true); if (shadowR != null) { mShadowCacheTextures.mCacheRects.put(key + shadowRadius, shadowR); } } glyph.dispose(); } if (r != null) { if (shadowR != null) { shadowR.mTexture.allocateMesh(); if (shadowR.mTexture.mFontBatch.full()) { flushBatch(); } shadowR.mTexture.mFontBatch.draw(x + shadowR.mLeft - shadowRadius + paint.getShadowDx(), baseline - shadowR.mTop - shadowRadius + paint.getShadowDy(), shadowR.mRect.width(), shadowR.mRect.height(), shadowR.mRect.rect().left, shadowR.mRect.rect().top, shadowR.mRect.width(), shadowR.mRect.height(), matrix, alpha, shadowColor, paint); } r.mTexture.allocateMesh(); if (r.mTexture.mFontBatch.full()) { flushBatch(); } r.mTexture.mFontBatch.draw(x + r.mLeft, baseline - r.mTop, r.mRect.width(), r.mRect.height(), r.mRect.rect().left, r.mRect.rect().top, r.mRect.width(), r.mRect.height(), matrix, alpha, paint.getColor(), paint); x += r.mGlyphSlot.getAdvanceX(); } } if (forceFinish) { flushBatch(); } } } private void flushAndInvalidate(FontCaches caches) { for (CacheTexture cacheTexture : caches.mACacheTextures) { if (cacheTexture.mFontBatch != null) { cacheTexture.mFontBatch.flush(); } cacheTexture.setDirty(false); cacheTexture.mPacker.reset(); } caches.mCacheRects.clear(); } private FontRect cacheBitmap(FontCaches caches, int w, int h, int border, FreeType.GlyphSlot slot, FreeType.Glyph glyph, FreeType.Bitmap bitmap, boolean c) { for (CacheTexture cacheTexture : caches.mACacheTextures) { PackerRect rect = cacheTexture.mPacker.insert(w + border * 2, h + border * 2); if (rect != null) { FontRect r = new FontRect(cacheTexture, rect, new GlyphSlot(FreeType.toInt(slot.getAdvanceX()), FreeType.toInt(slot.getAdvanceY())), glyph.getLeft(), glyph.getTop()); if (cacheTexture.getPixelBuffer() == null) { cacheTexture.allocateTexture(); } ByteBuffer byteBuffer = cacheTexture.getPixelBuffer().map(); ByteBuffer buffer = bitmap.getBuffer(); int pitch = bitmap.getPitch(); // for (int i = 0; i < rect.height(); i ++) { // for (int j = 0; j < rect.width(); j ++) { // if (i < border || i >= rect.height() - border || j < border || j >= rect.width() - border) { // byteBuffer.put((i + rect.rect().top) * cacheTexture.mWidth + j + rect.rect().left, (byte) 0); // } else { // byteBuffer.put((i + rect.rect().top) * cacheTexture.mWidth + j + rect.rect().left, buffer.get((i - border) * pitch + j - border)); // } // } // } FontUtils.loadGlyphBitmap(buffer, w, h, pitch, border, byteBuffer, cacheTexture.mWidth, cacheTexture.mHeight, rect.rect().left, rect.rect().top); cacheTexture.mDirtyRect.union(rect.rect()); cacheTexture.setDirty(true); return r; } } if (c) { flushAndInvalidate(caches); return cacheBitmap(caches, w, h, border, slot, glyph, bitmap, false); } return null; } /* * TODO 较耗时,需要优化。可以考虑将模糊操作通过其他工作线程处理 */ private FontRect cacheBitmapShadow(FontCaches caches, int w, int h, int shadowRadius, FreeType.GlyphSlot slot, FreeType.Glyph glyph, FreeType.Bitmap bitmap, boolean c) { for (CacheTexture cacheTexture : caches.mACacheTextures) { PackerRect rect = cacheTexture.mPacker.insert(w + shadowRadius * 2, h + shadowRadius * 2); if (rect != null) { FontRect r = new FontRect(cacheTexture, rect, new GlyphSlot(FreeType.toInt(slot.getAdvanceX()), FreeType.toInt(slot.getAdvanceY())), glyph.getLeft(), glyph.getTop()); if (cacheTexture.getPixelBuffer() == null) { cacheTexture.allocateTexture(); } ByteBuffer byteBuffer = cacheTexture.getPixelBuffer().map(); ByteBuffer buffer = bitmap.getBuffer(); int pitch = bitmap.getPitch(); int size = rect.width() * rect.height(); if (mBuffer.length < size) { mBuffer = new byte[GrowingArrayUtils.growSize(size)]; } // for (int i = 0; i < rect.height(); i ++) { // for (int j = 0; j < rect.width(); j ++) { // if (i < shadowRadius || i >= rect.height() - shadowRadius || j < shadowRadius || j >= rect.width() - shadowRadius) { // mBuffer[i* rect.width() + j] = (byte) 0; // } else { // mBuffer[i * rect.width() + j] = buffer.get((i - shadowRadius) * pitch + j - shadowRadius); // } // } // } FontUtils.loadGlyphBlurBitmap(buffer, w, h, pitch, mBuffer, shadowRadius); mBlurProcess.blur(mBuffer, rect.width(), rect.height(), rect.width(), shadowRadius); // for (int i = 0; i < rect.height(); i ++) { // for (int j = 0; j < rect.width(); j ++) { // byteBuffer.put((i + rect.rect().top) * cacheTexture.mWidth + j + rect.rect().left, mBuffer[i * rect.width() + j]); // } // } FontUtils.loadGlyphBitmap(mBuffer, rect.width(), rect.height(), rect.width(), 0, byteBuffer, cacheTexture.mWidth, cacheTexture.mHeight, rect.rect().left, rect.rect().top); cacheTexture.mDirtyRect.union(rect.rect()); cacheTexture.setDirty(true); return r; } } if (c) { flushAndInvalidate(caches); return cacheBitmapShadow(caches, w, h, shadowRadius, slot, glyph, bitmap, false); } return null; } private static class FontData { int ascent; int descent; int lineHeight; int spaceWidth; } }