diff options
-rw-r--r-- | libs/hwui/Android.mk | 2 | ||||
-rw-r--r-- | libs/hwui/FontRenderer.cpp | 590 | ||||
-rw-r--r-- | libs/hwui/FontRenderer.h | 254 | ||||
-rw-r--r-- | libs/hwui/font/CacheTexture.cpp | 172 | ||||
-rw-r--r-- | libs/hwui/font/CacheTexture.h | 122 | ||||
-rw-r--r-- | libs/hwui/font/CachedGlyphInfo.h | 58 | ||||
-rw-r--r-- | libs/hwui/font/Font.cpp | 446 | ||||
-rw-r--r-- | libs/hwui/font/Font.h | 130 | ||||
-rw-r--r-- | libs/hwui/font/FontUtil.h | 60 |
9 files changed, 1005 insertions, 829 deletions
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index c3a07a1..c0f79df 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -6,6 +6,8 @@ include $(CLEAR_VARS) ifeq ($(USE_OPENGL_RENDERER),true) LOCAL_SRC_FILES:= \ utils/SortedListImpl.cpp \ + font/CacheTexture.cpp \ + font/Font.cpp \ FontRenderer.cpp \ GammaFontRenderer.cpp \ Caches.cpp \ diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index 95ccdfc..e66ab54 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -25,585 +25,12 @@ #include "Caches.h" #include "Debug.h" #include "FontRenderer.h" -#include "Caches.h" +#include "Rect.h" namespace android { namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// -// Defines -/////////////////////////////////////////////////////////////////////////////// - -#define DEFAULT_TEXT_SMALL_CACHE_WIDTH 1024 -#define DEFAULT_TEXT_SMALL_CACHE_HEIGHT 256 -#define DEFAULT_TEXT_LARGE_CACHE_WIDTH 2048 -#define DEFAULT_TEXT_LARGE_CACHE_HEIGHT 512 -#define CACHE_BLOCK_ROUNDING_SIZE 4 - -#define AUTO_KERN(prev, next) (((next) - (prev) + 32) >> 6 << 16) - -/////////////////////////////////////////////////////////////////////////////// -// CacheBlock -/////////////////////////////////////////////////////////////////////////////// - -/** - * Insert new block into existing linked list of blocks. Blocks are sorted in increasing-width - * order, except for the final block (the remainder space at the right, since we fill from the - * left). - */ -CacheBlock* CacheBlock::insertBlock(CacheBlock* head, CacheBlock *newBlock) { -#if DEBUG_FONT_RENDERER - ALOGD("insertBlock: this, x, y, w, h = %p, %d, %d, %d, %d", - newBlock, newBlock->mX, newBlock->mY, - newBlock->mWidth, newBlock->mHeight); -#endif - CacheBlock *currBlock = head; - CacheBlock *prevBlock = NULL; - while (currBlock && currBlock->mY != TEXTURE_BORDER_SIZE) { - if (newBlock->mWidth < currBlock->mWidth) { - newBlock->mNext = currBlock; - newBlock->mPrev = prevBlock; - currBlock->mPrev = newBlock; - if (prevBlock) { - prevBlock->mNext = newBlock; - return head; - } else { - return newBlock; - } - } - prevBlock = currBlock; - currBlock = currBlock->mNext; - } - // new block larger than all others - insert at end (but before the remainder space, if there) - newBlock->mNext = currBlock; - newBlock->mPrev = prevBlock; - if (currBlock) { - currBlock->mPrev = newBlock; - } - if (prevBlock) { - prevBlock->mNext = newBlock; - return head; - } else { - return newBlock; - } -} - -CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock *blockToRemove) { -#if DEBUG_FONT_RENDERER - ALOGD("removeBlock: this, x, y, w, h = %p, %d, %d, %d, %d", - blockToRemove, blockToRemove->mX, blockToRemove->mY, - blockToRemove->mWidth, blockToRemove->mHeight); -#endif - CacheBlock* newHead = head; - CacheBlock* nextBlock = blockToRemove->mNext; - CacheBlock* prevBlock = blockToRemove->mPrev; - if (prevBlock) { - prevBlock->mNext = nextBlock; - } else { - newHead = nextBlock; - } - if (nextBlock) { - nextBlock->mPrev = prevBlock; - } - delete blockToRemove; - return newHead; -} - -/////////////////////////////////////////////////////////////////////////////// -// CacheTexture -/////////////////////////////////////////////////////////////////////////////// - -bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY) { - if (glyph.fHeight + TEXTURE_BORDER_SIZE > mHeight) { - return false; - } - - uint16_t glyphW = glyph.fWidth + TEXTURE_BORDER_SIZE; - uint16_t glyphH = glyph.fHeight + TEXTURE_BORDER_SIZE; - // roundedUpW equals glyphW to the next multiple of CACHE_BLOCK_ROUNDING_SIZE. - // This columns for glyphs that are close but not necessarily exactly the same size. It trades - // off the loss of a few pixels for some glyphs against the ability to store more glyphs - // of varying sizes in one block. - uint16_t roundedUpW = - (glyphW + CACHE_BLOCK_ROUNDING_SIZE - 1) & -CACHE_BLOCK_ROUNDING_SIZE; - CacheBlock *cacheBlock = mCacheBlocks; - while (cacheBlock) { - // Store glyph in this block iff: it fits the block's remaining space and: - // it's the remainder space (mY == 0) or there's only enough height for this one glyph - // or it's within ROUNDING_SIZE of the block width - if (roundedUpW <= cacheBlock->mWidth && glyphH <= cacheBlock->mHeight && - (cacheBlock->mY == TEXTURE_BORDER_SIZE || - (cacheBlock->mWidth - roundedUpW < CACHE_BLOCK_ROUNDING_SIZE))) { - if (cacheBlock->mHeight - glyphH < glyphH) { - // Only enough space for this glyph - don't bother rounding up the width - roundedUpW = glyphW; - } - *retOriginX = cacheBlock->mX; - *retOriginY = cacheBlock->mY; - // If this is the remainder space, create a new cache block for this column. Otherwise, - // adjust the info about this column. - if (cacheBlock->mY == TEXTURE_BORDER_SIZE) { - uint16_t oldX = cacheBlock->mX; - // Adjust remainder space dimensions - cacheBlock->mWidth -= roundedUpW; - cacheBlock->mX += roundedUpW; - if (mHeight - glyphH >= glyphH) { - // There's enough height left over to create a new CacheBlock - CacheBlock *newBlock = new CacheBlock(oldX, glyphH + TEXTURE_BORDER_SIZE, - roundedUpW, mHeight - glyphH - TEXTURE_BORDER_SIZE); -#if DEBUG_FONT_RENDERER - ALOGD("fitBitmap: Created new block: this, x, y, w, h = %p, %d, %d, %d, %d", - newBlock, newBlock->mX, newBlock->mY, - newBlock->mWidth, newBlock->mHeight); -#endif - mCacheBlocks = CacheBlock::insertBlock(mCacheBlocks, newBlock); - } - } else { - // Insert into current column and adjust column dimensions - cacheBlock->mY += glyphH; - cacheBlock->mHeight -= glyphH; -#if DEBUG_FONT_RENDERER - ALOGD("fitBitmap: Added to existing block: this, x, y, w, h = %p, %d, %d, %d, %d", - cacheBlock, cacheBlock->mX, cacheBlock->mY, - cacheBlock->mWidth, cacheBlock->mHeight); -#endif - } - if (cacheBlock->mHeight < fmin(glyphH, glyphW)) { - // If remaining space in this block is too small to be useful, remove it - mCacheBlocks = CacheBlock::removeBlock(mCacheBlocks, cacheBlock); - } - mDirty = true; -#if DEBUG_FONT_RENDERER - ALOGD("fitBitmap: current block list:"); - mCacheBlocks->output(); -#endif - ++mNumGlyphs; - return true; - } - cacheBlock = cacheBlock->mNext; - } -#if DEBUG_FONT_RENDERER - ALOGD("fitBitmap: returning false for glyph of size %d, %d", glyphW, glyphH); -#endif - return false; -} - -/////////////////////////////////////////////////////////////////////////////// -// Font -/////////////////////////////////////////////////////////////////////////////// - -Font::Font(FontRenderer* state, uint32_t fontId, float fontSize, - int flags, uint32_t italicStyle, uint32_t scaleX, - SkPaint::Style style, uint32_t strokeWidth) : - mState(state), mFontId(fontId), mFontSize(fontSize), - mFlags(flags), mItalicStyle(italicStyle), mScaleX(scaleX), - mStyle(style), mStrokeWidth(mStrokeWidth) { -} - - -Font::~Font() { - for (uint32_t ct = 0; ct < mState->mActiveFonts.size(); ct++) { - if (mState->mActiveFonts[ct] == this) { - mState->mActiveFonts.removeAt(ct); - break; - } - } - - for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) { - delete mCachedGlyphs.valueAt(i); - } -} - -void Font::invalidateTextureCache(CacheTexture *cacheTexture) { - for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) { - CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueAt(i); - if (cacheTexture == NULL || cachedGlyph->mCacheTexture == cacheTexture) { - cachedGlyph->mIsValid = false; - } - } -} - -void Font::measureCachedGlyph(CachedGlyphInfo *glyph, int x, int y, - uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) { - int nPenX = x + glyph->mBitmapLeft; - int nPenY = y + glyph->mBitmapTop; - - int width = (int) glyph->mBitmapWidth; - int height = (int) glyph->mBitmapHeight; - - if (bounds->bottom > nPenY) { - bounds->bottom = nPenY; - } - if (bounds->left > nPenX) { - bounds->left = nPenX; - } - if (bounds->right < nPenX + width) { - bounds->right = nPenX + width; - } - if (bounds->top < nPenY + height) { - bounds->top = nPenY + height; - } -} - -void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y, - uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) { - int nPenX = x + glyph->mBitmapLeft; - int nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight; - - float u1 = glyph->mBitmapMinU; - float u2 = glyph->mBitmapMaxU; - float v1 = glyph->mBitmapMinV; - float v2 = glyph->mBitmapMaxV; - - int width = (int) glyph->mBitmapWidth; - int height = (int) glyph->mBitmapHeight; - - mState->appendMeshQuad(nPenX, nPenY, u1, v2, - nPenX + width, nPenY, u2, v2, - nPenX + width, nPenY - height, u2, v1, - nPenX, nPenY - height, u1, v1, glyph->mCacheTexture); -} - -void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y, - uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) { - int nPenX = x + glyph->mBitmapLeft; - int nPenY = y + glyph->mBitmapTop; - - uint32_t endX = glyph->mStartX + glyph->mBitmapWidth; - uint32_t endY = glyph->mStartY + glyph->mBitmapHeight; - - CacheTexture *cacheTexture = glyph->mCacheTexture; - uint32_t cacheWidth = cacheTexture->mWidth; - const uint8_t* cacheBuffer = cacheTexture->mTexture; - - uint32_t cacheX = 0, cacheY = 0; - int32_t bX = 0, bY = 0; - for (cacheX = glyph->mStartX, bX = nPenX; cacheX < endX; cacheX++, bX++) { - for (cacheY = glyph->mStartY, bY = nPenY; cacheY < endY; cacheY++, bY++) { -#if DEBUG_FONT_RENDERER - if (bX < 0 || bY < 0 || bX >= (int32_t) bitmapW || bY >= (int32_t) bitmapH) { - ALOGE("Skipping invalid index"); - continue; - } -#endif - uint8_t tempCol = cacheBuffer[cacheY * cacheWidth + cacheX]; - bitmap[bY * bitmapW + bX] = tempCol; - } - } -} - -void Font::drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float vOffset, - SkPathMeasure& measure, SkPoint* position, SkVector* tangent) { - const float halfWidth = glyph->mBitmapWidth * 0.5f; - const float height = glyph->mBitmapHeight; - - vOffset += glyph->mBitmapTop + height; - - SkPoint destination[4]; - measure.getPosTan(x + hOffset + glyph->mBitmapLeft + halfWidth, position, tangent); - - // Move along the tangent and offset by the normal - destination[0].set(-tangent->fX * halfWidth - tangent->fY * vOffset, - -tangent->fY * halfWidth + tangent->fX * vOffset); - destination[1].set(tangent->fX * halfWidth - tangent->fY * vOffset, - tangent->fY * halfWidth + tangent->fX * vOffset); - destination[2].set(destination[1].fX + tangent->fY * height, - destination[1].fY - tangent->fX * height); - destination[3].set(destination[0].fX + tangent->fY * height, - destination[0].fY - tangent->fX * height); - - const float u1 = glyph->mBitmapMinU; - const float u2 = glyph->mBitmapMaxU; - const float v1 = glyph->mBitmapMinV; - const float v2 = glyph->mBitmapMaxV; - - mState->appendRotatedMeshQuad( - position->fX + destination[0].fX, - position->fY + destination[0].fY, u1, v2, - position->fX + destination[1].fX, - position->fY + destination[1].fY, u2, v2, - position->fX + destination[2].fX, - position->fY + destination[2].fY, u2, v1, - position->fX + destination[3].fX, - position->fY + destination[3].fY, u1, v1, - glyph->mCacheTexture); -} - -CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit, bool precaching) { - CachedGlyphInfo* cachedGlyph = NULL; - ssize_t index = mCachedGlyphs.indexOfKey(textUnit); - if (index >= 0) { - cachedGlyph = mCachedGlyphs.valueAt(index); - } else { - cachedGlyph = cacheGlyph(paint, textUnit, precaching); - } - - // Is the glyph still in texture cache? - if (!cachedGlyph->mIsValid) { - const SkGlyph& skiaGlyph = GET_METRICS(paint, textUnit); - updateGlyphCache(paint, skiaGlyph, cachedGlyph, precaching); - } - - return cachedGlyph; -} - -void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len, - int numGlyphs, int x, int y, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) { - if (bitmap != NULL && bitmapW > 0 && bitmapH > 0) { - render(paint, text, start, len, numGlyphs, x, y, BITMAP, bitmap, - bitmapW, bitmapH, NULL, NULL); - } else { - render(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, NULL, - 0, 0, NULL, NULL); - } -} - -void Font::render(SkPaint* paint, const char *text, uint32_t start, uint32_t len, - int numGlyphs, int x, int y, const float* positions) { - render(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, NULL, - 0, 0, NULL, positions); -} - -void Font::render(SkPaint* paint, const char *text, uint32_t start, uint32_t len, - int numGlyphs, SkPath* path, float hOffset, float vOffset) { - if (numGlyphs == 0 || text == NULL || len == 0) { - return; - } - - text += start; - - int glyphsCount = 0; - SkFixed prevRsbDelta = 0; - - float penX = 0.0f; - - SkPoint position; - SkVector tangent; - - SkPathMeasure measure(*path, false); - float pathLength = SkScalarToFloat(measure.getLength()); - - if (paint->getTextAlign() != SkPaint::kLeft_Align) { - float textWidth = SkScalarToFloat(paint->measureText(text, len)); - float pathOffset = pathLength; - if (paint->getTextAlign() == SkPaint::kCenter_Align) { - textWidth *= 0.5f; - pathOffset *= 0.5f; - } - penX += pathOffset - textWidth; - } - - while (glyphsCount < numGlyphs && penX < pathLength) { - glyph_t glyph = GET_GLYPH(text); - - if (IS_END_OF_STRING(glyph)) { - break; - } - - CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph); - penX += SkFixedToFloat(AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta)); - prevRsbDelta = cachedGlyph->mRsbDelta; - - if (cachedGlyph->mIsValid) { - drawCachedGlyph(cachedGlyph, penX, hOffset, vOffset, measure, &position, &tangent); - } - - penX += SkFixedToFloat(cachedGlyph->mAdvanceX); - - glyphsCount++; - } -} - -void Font::measure(SkPaint* paint, const char* text, uint32_t start, uint32_t len, - int numGlyphs, Rect *bounds, const float* positions) { - if (bounds == NULL) { - ALOGE("No return rectangle provided to measure text"); - return; - } - bounds->set(1e6, -1e6, -1e6, 1e6); - render(paint, text, start, len, numGlyphs, 0, 0, MEASURE, NULL, 0, 0, bounds, positions); -} - -void Font::precache(SkPaint* paint, const char* text, int numGlyphs) { - - if (numGlyphs == 0 || text == NULL) { - return; - } - int glyphsCount = 0; - - while (glyphsCount < numGlyphs) { - glyph_t glyph = GET_GLYPH(text); - - // Reached the end of the string - if (IS_END_OF_STRING(glyph)) { - break; - } - - CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph, true); - - glyphsCount++; - } -} - -void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len, - int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap, - uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* positions) { - if (numGlyphs == 0 || text == NULL || len == 0) { - return; - } - - static RenderGlyph gRenderGlyph[] = { - &android::uirenderer::Font::drawCachedGlyph, - &android::uirenderer::Font::drawCachedGlyphBitmap, - &android::uirenderer::Font::measureCachedGlyph - }; - RenderGlyph render = gRenderGlyph[mode]; - - text += start; - int glyphsCount = 0; - - if (CC_LIKELY(positions == NULL)) { - SkFixed prevRsbDelta = 0; - - float penX = x + 0.5f; - int penY = y; - - while (glyphsCount < numGlyphs) { - glyph_t glyph = GET_GLYPH(text); - - // Reached the end of the string - if (IS_END_OF_STRING(glyph)) { - break; - } - - CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph); - penX += SkFixedToFloat(AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta)); - prevRsbDelta = cachedGlyph->mRsbDelta; - - // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage - if (cachedGlyph->mIsValid) { - (*this.*render)(cachedGlyph, (int) floorf(penX), penY, - bitmap, bitmapW, bitmapH, bounds, positions); - } - - penX += SkFixedToFloat(cachedGlyph->mAdvanceX); - - glyphsCount++; - } - } else { - const SkPaint::Align align = paint->getTextAlign(); - - // This is for renderPosText() - while (glyphsCount < numGlyphs) { - glyph_t glyph = GET_GLYPH(text); - - // Reached the end of the string - if (IS_END_OF_STRING(glyph)) { - break; - } - - CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph); - - // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage - if (cachedGlyph->mIsValid) { - int penX = x + positions[(glyphsCount << 1)]; - int penY = y + positions[(glyphsCount << 1) + 1]; - - switch (align) { - case SkPaint::kRight_Align: - penX -= SkFixedToFloat(cachedGlyph->mAdvanceX); - penY -= SkFixedToFloat(cachedGlyph->mAdvanceY); - break; - case SkPaint::kCenter_Align: - penX -= SkFixedToFloat(cachedGlyph->mAdvanceX >> 1); - penY -= SkFixedToFloat(cachedGlyph->mAdvanceY >> 1); - default: - break; - } - - (*this.*render)(cachedGlyph, penX, penY, - bitmap, bitmapW, bitmapH, bounds, positions); - } - - glyphsCount++; - } - } -} - -void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph, - bool precaching) { - glyph->mAdvanceX = skiaGlyph.fAdvanceX; - glyph->mAdvanceY = skiaGlyph.fAdvanceY; - glyph->mBitmapLeft = skiaGlyph.fLeft; - glyph->mBitmapTop = skiaGlyph.fTop; - glyph->mLsbDelta = skiaGlyph.fLsbDelta; - glyph->mRsbDelta = skiaGlyph.fRsbDelta; - - uint32_t startX = 0; - uint32_t startY = 0; - - // Get the bitmap for the glyph - paint->findImage(skiaGlyph); - mState->cacheBitmap(skiaGlyph, glyph, &startX, &startY, precaching); - - if (!glyph->mIsValid) { - return; - } - - uint32_t endX = startX + skiaGlyph.fWidth; - uint32_t endY = startY + skiaGlyph.fHeight; - - glyph->mStartX = startX; - glyph->mStartY = startY; - glyph->mBitmapWidth = skiaGlyph.fWidth; - glyph->mBitmapHeight = skiaGlyph.fHeight; - - uint32_t cacheWidth = glyph->mCacheTexture->mWidth; - uint32_t cacheHeight = glyph->mCacheTexture->mHeight; - - glyph->mBitmapMinU = startX / (float) cacheWidth; - glyph->mBitmapMinV = startY / (float) cacheHeight; - glyph->mBitmapMaxU = endX / (float) cacheWidth; - glyph->mBitmapMaxV = endY / (float) cacheHeight; - - mState->mUploadTexture = true; -} - -CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching) { - CachedGlyphInfo* newGlyph = new CachedGlyphInfo(); - mCachedGlyphs.add(glyph, newGlyph); - - const SkGlyph& skiaGlyph = GET_METRICS(paint, glyph); - newGlyph->mGlyphIndex = skiaGlyph.fID; - newGlyph->mIsValid = false; - - updateGlyphCache(paint, skiaGlyph, newGlyph, precaching); - - return newGlyph; -} - -Font* Font::create(FontRenderer* state, uint32_t fontId, float fontSize, - int flags, uint32_t italicStyle, uint32_t scaleX, - SkPaint::Style style, uint32_t strokeWidth) { - Vector<Font*> &activeFonts = state->mActiveFonts; - - for (uint32_t i = 0; i < activeFonts.size(); i++) { - Font* font = activeFonts[i]; - if (font->mFontId == fontId && font->mFontSize == fontSize && - font->mFlags == flags && font->mItalicStyle == italicStyle && - font->mScaleX == scaleX && font->mStyle == style && - (style == SkPaint::kFill_Style || font->mStrokeWidth == strokeWidth)) { - return font; - } - } - - Font* newFont = new Font(state, fontId, fontSize, flags, italicStyle, - scaleX, style, strokeWidth); - activeFonts.push(newFont); - return newFont; -} - -/////////////////////////////////////////////////////////////////////////////// // FontRenderer /////////////////////////////////////////////////////////////////////////////// @@ -636,20 +63,25 @@ FontRenderer::FontRenderer() { if (property_get(PROPERTY_TEXT_SMALL_CACHE_WIDTH, property, NULL) > 0) { mSmallCacheWidth = atoi(property); } + if (property_get(PROPERTY_TEXT_SMALL_CACHE_HEIGHT, property, NULL) > 0) { mSmallCacheHeight = atoi(property); } + if (property_get(PROPERTY_TEXT_LARGE_CACHE_WIDTH, property, NULL) > 0) { mLargeCacheWidth = atoi(property); } + if (property_get(PROPERTY_TEXT_LARGE_CACHE_HEIGHT, property, NULL) > 0) { mLargeCacheHeight = atoi(property); } - GLint maxTextureSize = Caches::getInstance().maxTextureSize; - mSmallCacheWidth = (mSmallCacheWidth > maxTextureSize) ? maxTextureSize : mSmallCacheWidth; - mSmallCacheHeight = (mSmallCacheHeight > maxTextureSize) ? maxTextureSize : mSmallCacheHeight; - mLargeCacheWidth = (mLargeCacheWidth > maxTextureSize) ? maxTextureSize : mLargeCacheWidth; - mLargeCacheHeight = (mLargeCacheHeight > maxTextureSize) ? maxTextureSize : mLargeCacheHeight; + + uint32_t maxTextureSize = (uint32_t) Caches::getInstance().maxTextureSize; + mSmallCacheWidth = mSmallCacheWidth > maxTextureSize ? maxTextureSize : mSmallCacheWidth; + mSmallCacheHeight = mSmallCacheHeight > maxTextureSize ? maxTextureSize : mSmallCacheHeight; + mLargeCacheWidth = mLargeCacheWidth > maxTextureSize ? maxTextureSize : mLargeCacheWidth; + mLargeCacheHeight = mLargeCacheHeight > maxTextureSize ? maxTextureSize : mLargeCacheHeight; + if (sLogFontRendererCreate) { INIT_LOGD(" Text cache sizes, in pixels: %i x %i, %i x %i, %i x %i, %i x %i", mSmallCacheWidth, mSmallCacheHeight, diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h index 241b73e..58aa9c3 100644 --- a/libs/hwui/FontRenderer.h +++ b/libs/hwui/FontRenderer.h @@ -17,268 +17,22 @@ #ifndef ANDROID_HWUI_FONT_RENDERER_H #define ANDROID_HWUI_FONT_RENDERER_H -#include <utils/String8.h> -#include <utils/String16.h> #include <utils/Vector.h> -#include <utils/KeyedVector.h> -#include <SkScalerContext.h> #include <SkPaint.h> -#include <SkPathMeasure.h> -#include <SkPoint.h> #include <GLES2/gl2.h> -#include "Rect.h" +#include "font/FontUtil.h" +#include "font/CacheTexture.h" +#include "font/CachedGlyphInfo.h" +#include "font/Font.h" #include "Properties.h" namespace android { namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// -// Defines -/////////////////////////////////////////////////////////////////////////////// - -#if RENDER_TEXT_AS_GLYPHS - typedef uint16_t glyph_t; - #define TO_GLYPH(g) g - #define GET_METRICS(paint, glyph) paint->getGlyphMetrics(glyph) - #define GET_GLYPH(text) nextGlyph((const uint16_t**) &text) - #define IS_END_OF_STRING(glyph) false -#else - typedef SkUnichar glyph_t; - #define TO_GLYPH(g) ((SkUnichar) g) - #define GET_METRICS(paint, glyph) paint->getUnicharMetrics(glyph) - #define GET_GLYPH(text) SkUTF16_NextUnichar((const uint16_t**) &text) - #define IS_END_OF_STRING(glyph) glyph < 0 -#endif - -#define TEXTURE_BORDER_SIZE 1 - -/////////////////////////////////////////////////////////////////////////////// -// Declarations -/////////////////////////////////////////////////////////////////////////////// - -class FontRenderer; - -/** - * CacheBlock is a node in a linked list of current free space areas in a CacheTexture. - * Using CacheBlocks enables us to pack the cache from top to bottom as well as left to right. - * When we add a glyph to the cache, we see if it fits within one of the existing columns that - * have already been started (this is the case if the glyph fits vertically as well as - * horizontally, and if its width is sufficiently close to the column width to avoid - * sub-optimal packing of small glyphs into wide columns). If there is no column in which the - * glyph fits, we check the final node, which is the remaining space in the cache, creating - * a new column as appropriate. - * - * As columns fill up, we remove their CacheBlock from the list to avoid having to check - * small blocks in the future. - */ -struct CacheBlock { - uint16_t mX; - uint16_t mY; - uint16_t mWidth; - uint16_t mHeight; - CacheBlock* mNext; - CacheBlock* mPrev; - - CacheBlock(uint16_t x, uint16_t y, uint16_t width, uint16_t height, bool empty = false): - mX(x), mY(y), mWidth(width), mHeight(height), mNext(NULL), mPrev(NULL) - { - } - - static CacheBlock* insertBlock(CacheBlock* head, CacheBlock *newBlock); - - static CacheBlock* removeBlock(CacheBlock* head, CacheBlock *blockToRemove); - - void output() { - CacheBlock *currBlock = this; - while (currBlock) { - ALOGD("Block: this, x, y, w, h = %p, %d, %d, %d, %d", - currBlock, currBlock->mX, currBlock->mY, currBlock->mWidth, currBlock->mHeight); - currBlock = currBlock->mNext; - } - } -}; - -class CacheTexture { -public: - CacheTexture(uint16_t width, uint16_t height) : - mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height), - mLinearFiltering(false), mDirty(false), mNumGlyphs(0) { - mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE, - mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true); - } - - ~CacheTexture() { - if (mTexture) { - delete[] mTexture; - } - if (mTextureId) { - glDeleteTextures(1, &mTextureId); - } - reset(); - } - - void reset() { - // Delete existing cache blocks - while (mCacheBlocks != NULL) { - CacheBlock* tmpBlock = mCacheBlocks; - mCacheBlocks = mCacheBlocks->mNext; - delete tmpBlock; - } - mNumGlyphs = 0; - } - - void init() { - // reset, then create a new remainder space to start again - reset(); - mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE, - mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true); - } - - bool fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY); - - uint8_t* mTexture; - GLuint mTextureId; - uint16_t mWidth; - uint16_t mHeight; - bool mLinearFiltering; - bool mDirty; - uint16_t mNumGlyphs; - CacheBlock* mCacheBlocks; -}; - -struct CachedGlyphInfo { - // Has the cache been invalidated? - bool mIsValid; - // Location of the cached glyph in the bitmap - // in case we need to resize the texture or - // render to bitmap - uint32_t mStartX; - uint32_t mStartY; - uint32_t mBitmapWidth; - uint32_t mBitmapHeight; - // Also cache texture coords for the quad - float mBitmapMinU; - float mBitmapMinV; - float mBitmapMaxU; - float mBitmapMaxV; - // Minimize how much we call freetype - uint32_t mGlyphIndex; - uint32_t mAdvanceX; - uint32_t mAdvanceY; - // Values below contain a glyph's origin in the bitmap - int32_t mBitmapLeft; - int32_t mBitmapTop; - // Auto-kerning - SkFixed mLsbDelta; - SkFixed mRsbDelta; - CacheTexture* mCacheTexture; -}; - - -/////////////////////////////////////////////////////////////////////////////// -// Font -/////////////////////////////////////////////////////////////////////////////// - -/** - * Represents a font, defined by a Skia font id and a font size. A font is used - * to generate glyphs and cache them in the FontState. - */ -class Font { -public: - enum Style { - kFakeBold = 1 - }; - - ~Font(); - - /** - * Renders the specified string of text. - * If bitmap is specified, it will be used as the render target - */ - void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len, - int numGlyphs, int x, int y, uint8_t *bitmap = NULL, - uint32_t bitmapW = 0, uint32_t bitmapH = 0); - - void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len, - int numGlyphs, int x, int y, const float* positions); - - void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len, - int numGlyphs, SkPath* path, float hOffset, float vOffset); - - /** - * Creates a new font associated with the specified font state. - */ - static Font* create(FontRenderer* state, uint32_t fontId, float fontSize, - int flags, uint32_t italicStyle, uint32_t scaleX, SkPaint::Style style, - uint32_t strokeWidth); - -protected: - friend class FontRenderer; - typedef void (Font::*RenderGlyph)(CachedGlyphInfo*, int, int, uint8_t*, - uint32_t, uint32_t, Rect*, const float*); - - enum RenderMode { - FRAMEBUFFER, - BITMAP, - MEASURE, - }; - - void precache(SkPaint* paint, const char* text, int numGlyphs); - - void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len, - int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap, - uint32_t bitmapW, uint32_t bitmapH, Rect *bounds, const float* positions); - - void measure(SkPaint* paint, const char* text, uint32_t start, uint32_t len, - int numGlyphs, Rect *bounds, const float* positions); - - Font(FontRenderer* state, uint32_t fontId, float fontSize, int flags, uint32_t italicStyle, - uint32_t scaleX, SkPaint::Style style, uint32_t strokeWidth); - - // Cache of glyphs - DefaultKeyedVector<glyph_t, CachedGlyphInfo*> mCachedGlyphs; - - void invalidateTextureCache(CacheTexture *cacheTexture = NULL); - - CachedGlyphInfo* cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching); - void updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph, - bool precaching); - - void measureCachedGlyph(CachedGlyphInfo* glyph, int x, int y, - uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH, - Rect* bounds, const float* pos); - void drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y, - uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH, - Rect* bounds, const float* pos); - void drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y, - uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH, - Rect* bounds, const float* pos); - void drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float vOffset, - SkPathMeasure& measure, SkPoint* position, SkVector* tangent); - - CachedGlyphInfo* getCachedGlyph(SkPaint* paint, glyph_t textUnit, bool precaching = false); - - static glyph_t nextGlyph(const uint16_t** srcPtr) { - const uint16_t* src = *srcPtr; - glyph_t g = *src++; - *srcPtr = src; - return g; - } - - FontRenderer* mState; - uint32_t mFontId; - float mFontSize; - int mFlags; - uint32_t mItalicStyle; - uint32_t mScaleX; - SkPaint::Style mStyle; - uint32_t mStrokeWidth; -}; - -/////////////////////////////////////////////////////////////////////////////// // Renderer /////////////////////////////////////////////////////////////////////////////// diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp new file mode 100644 index 0000000..5fb2636 --- /dev/null +++ b/libs/hwui/font/CacheTexture.cpp @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <utils/Log.h> + +#include "Debug.h" +#include "CacheTexture.h" + +namespace android { +namespace uirenderer { + +/////////////////////////////////////////////////////////////////////////////// +// CacheBlock +/////////////////////////////////////////////////////////////////////////////// + +/** + * Insert new block into existing linked list of blocks. Blocks are sorted in increasing-width + * order, except for the final block (the remainder space at the right, since we fill from the + * left). + */ +CacheBlock* CacheBlock::insertBlock(CacheBlock* head, CacheBlock *newBlock) { +#if DEBUG_FONT_RENDERER + ALOGD("insertBlock: this, x, y, w, h = %p, %d, %d, %d, %d", + newBlock, newBlock->mX, newBlock->mY, + newBlock->mWidth, newBlock->mHeight); +#endif + CacheBlock *currBlock = head; + CacheBlock *prevBlock = NULL; + while (currBlock && currBlock->mY != TEXTURE_BORDER_SIZE) { + if (newBlock->mWidth < currBlock->mWidth) { + newBlock->mNext = currBlock; + newBlock->mPrev = prevBlock; + currBlock->mPrev = newBlock; + if (prevBlock) { + prevBlock->mNext = newBlock; + return head; + } else { + return newBlock; + } + } + prevBlock = currBlock; + currBlock = currBlock->mNext; + } + // new block larger than all others - insert at end (but before the remainder space, if there) + newBlock->mNext = currBlock; + newBlock->mPrev = prevBlock; + if (currBlock) { + currBlock->mPrev = newBlock; + } + if (prevBlock) { + prevBlock->mNext = newBlock; + return head; + } else { + return newBlock; + } +} + +CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock *blockToRemove) { +#if DEBUG_FONT_RENDERER + ALOGD("removeBlock: this, x, y, w, h = %p, %d, %d, %d, %d", + blockToRemove, blockToRemove->mX, blockToRemove->mY, + blockToRemove->mWidth, blockToRemove->mHeight); +#endif + CacheBlock* newHead = head; + CacheBlock* nextBlock = blockToRemove->mNext; + CacheBlock* prevBlock = blockToRemove->mPrev; + if (prevBlock) { + prevBlock->mNext = nextBlock; + } else { + newHead = nextBlock; + } + if (nextBlock) { + nextBlock->mPrev = prevBlock; + } + delete blockToRemove; + return newHead; +} + +/////////////////////////////////////////////////////////////////////////////// +// CacheTexture +/////////////////////////////////////////////////////////////////////////////// + +bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY) { + if (glyph.fHeight + TEXTURE_BORDER_SIZE > mHeight) { + return false; + } + + uint16_t glyphW = glyph.fWidth + TEXTURE_BORDER_SIZE; + uint16_t glyphH = glyph.fHeight + TEXTURE_BORDER_SIZE; + // roundedUpW equals glyphW to the next multiple of CACHE_BLOCK_ROUNDING_SIZE. + // This columns for glyphs that are close but not necessarily exactly the same size. It trades + // off the loss of a few pixels for some glyphs against the ability to store more glyphs + // of varying sizes in one block. + uint16_t roundedUpW = + (glyphW + CACHE_BLOCK_ROUNDING_SIZE - 1) & -CACHE_BLOCK_ROUNDING_SIZE; + CacheBlock *cacheBlock = mCacheBlocks; + while (cacheBlock) { + // Store glyph in this block iff: it fits the block's remaining space and: + // it's the remainder space (mY == 0) or there's only enough height for this one glyph + // or it's within ROUNDING_SIZE of the block width + if (roundedUpW <= cacheBlock->mWidth && glyphH <= cacheBlock->mHeight && + (cacheBlock->mY == TEXTURE_BORDER_SIZE || + (cacheBlock->mWidth - roundedUpW < CACHE_BLOCK_ROUNDING_SIZE))) { + if (cacheBlock->mHeight - glyphH < glyphH) { + // Only enough space for this glyph - don't bother rounding up the width + roundedUpW = glyphW; + } + *retOriginX = cacheBlock->mX; + *retOriginY = cacheBlock->mY; + // If this is the remainder space, create a new cache block for this column. Otherwise, + // adjust the info about this column. + if (cacheBlock->mY == TEXTURE_BORDER_SIZE) { + uint16_t oldX = cacheBlock->mX; + // Adjust remainder space dimensions + cacheBlock->mWidth -= roundedUpW; + cacheBlock->mX += roundedUpW; + if (mHeight - glyphH >= glyphH) { + // There's enough height left over to create a new CacheBlock + CacheBlock *newBlock = new CacheBlock(oldX, glyphH + TEXTURE_BORDER_SIZE, + roundedUpW, mHeight - glyphH - TEXTURE_BORDER_SIZE); +#if DEBUG_FONT_RENDERER + ALOGD("fitBitmap: Created new block: this, x, y, w, h = %p, %d, %d, %d, %d", + newBlock, newBlock->mX, newBlock->mY, + newBlock->mWidth, newBlock->mHeight); +#endif + mCacheBlocks = CacheBlock::insertBlock(mCacheBlocks, newBlock); + } + } else { + // Insert into current column and adjust column dimensions + cacheBlock->mY += glyphH; + cacheBlock->mHeight -= glyphH; +#if DEBUG_FONT_RENDERER + ALOGD("fitBitmap: Added to existing block: this, x, y, w, h = %p, %d, %d, %d, %d", + cacheBlock, cacheBlock->mX, cacheBlock->mY, + cacheBlock->mWidth, cacheBlock->mHeight); +#endif + } + if (cacheBlock->mHeight < fmin(glyphH, glyphW)) { + // If remaining space in this block is too small to be useful, remove it + mCacheBlocks = CacheBlock::removeBlock(mCacheBlocks, cacheBlock); + } + mDirty = true; +#if DEBUG_FONT_RENDERER + ALOGD("fitBitmap: current block list:"); + mCacheBlocks->output(); +#endif + ++mNumGlyphs; + return true; + } + cacheBlock = cacheBlock->mNext; + } +#if DEBUG_FONT_RENDERER + ALOGD("fitBitmap: returning false for glyph of size %d, %d", glyphW, glyphH); +#endif + return false; +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/font/CacheTexture.h b/libs/hwui/font/CacheTexture.h new file mode 100644 index 0000000..7840d74 --- /dev/null +++ b/libs/hwui/font/CacheTexture.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_CACHE_TEXTURE_H +#define ANDROID_HWUI_CACHE_TEXTURE_H + +#include <GLES2/gl2.h> + +#include <SkScalerContext.h> + +#include <utils/Log.h> + +#include "FontUtil.h" + +namespace android { +namespace uirenderer { + +/** + * CacheBlock is a node in a linked list of current free space areas in a CacheTexture. + * Using CacheBlocks enables us to pack the cache from top to bottom as well as left to right. + * When we add a glyph to the cache, we see if it fits within one of the existing columns that + * have already been started (this is the case if the glyph fits vertically as well as + * horizontally, and if its width is sufficiently close to the column width to avoid + * sub-optimal packing of small glyphs into wide columns). If there is no column in which the + * glyph fits, we check the final node, which is the remaining space in the cache, creating + * a new column as appropriate. + * + * As columns fill up, we remove their CacheBlock from the list to avoid having to check + * small blocks in the future. + */ +struct CacheBlock { + uint16_t mX; + uint16_t mY; + uint16_t mWidth; + uint16_t mHeight; + CacheBlock* mNext; + CacheBlock* mPrev; + + CacheBlock(uint16_t x, uint16_t y, uint16_t width, uint16_t height, bool empty = false): + mX(x), mY(y), mWidth(width), mHeight(height), mNext(NULL), mPrev(NULL) + { + } + + static CacheBlock* insertBlock(CacheBlock* head, CacheBlock *newBlock); + + static CacheBlock* removeBlock(CacheBlock* head, CacheBlock *blockToRemove); + + void output() { + CacheBlock *currBlock = this; + while (currBlock) { + ALOGD("Block: this, x, y, w, h = %p, %d, %d, %d, %d", + currBlock, currBlock->mX, currBlock->mY, currBlock->mWidth, currBlock->mHeight); + currBlock = currBlock->mNext; + } + } +}; + +class CacheTexture { +public: + CacheTexture(uint16_t width, uint16_t height) : + mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height), + mLinearFiltering(false), mDirty(false), mNumGlyphs(0) { + mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE, + mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true); + } + + ~CacheTexture() { + if (mTexture) { + delete[] mTexture; + } + if (mTextureId) { + glDeleteTextures(1, &mTextureId); + } + reset(); + } + + void reset() { + // Delete existing cache blocks + while (mCacheBlocks != NULL) { + CacheBlock* tmpBlock = mCacheBlocks; + mCacheBlocks = mCacheBlocks->mNext; + delete tmpBlock; + } + mNumGlyphs = 0; + } + + void init() { + // reset, then create a new remainder space to start again + reset(); + mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE, + mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true); + } + + bool fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY); + + uint8_t* mTexture; + GLuint mTextureId; + uint16_t mWidth; + uint16_t mHeight; + bool mLinearFiltering; + bool mDirty; + uint16_t mNumGlyphs; + CacheBlock* mCacheBlocks; +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_CACHE_TEXTURE_H diff --git a/libs/hwui/font/CachedGlyphInfo.h b/libs/hwui/font/CachedGlyphInfo.h new file mode 100644 index 0000000..6680a00 --- /dev/null +++ b/libs/hwui/font/CachedGlyphInfo.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_CACHED_GLYPH_INFO_H +#define ANDROID_HWUI_CACHED_GLYPH_INFO_H + +#include <SkFixed.h> + +#include "CacheTexture.h" + +namespace android { +namespace uirenderer { + +struct CachedGlyphInfo { + // Has the cache been invalidated? + bool mIsValid; + // Location of the cached glyph in the bitmap + // in case we need to resize the texture or + // render to bitmap + uint32_t mStartX; + uint32_t mStartY; + uint32_t mBitmapWidth; + uint32_t mBitmapHeight; + // Also cache texture coords for the quad + float mBitmapMinU; + float mBitmapMinV; + float mBitmapMaxU; + float mBitmapMaxV; + // Minimize how much we call freetype + uint32_t mGlyphIndex; + uint32_t mAdvanceX; + uint32_t mAdvanceY; + // Values below contain a glyph's origin in the bitmap + int32_t mBitmapLeft; + int32_t mBitmapTop; + // Auto-kerning + SkFixed mLsbDelta; + SkFixed mRsbDelta; + CacheTexture* mCacheTexture; +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_CACHED_GLYPH_INFO_H diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp new file mode 100644 index 0000000..0439875 --- /dev/null +++ b/libs/hwui/font/Font.cpp @@ -0,0 +1,446 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cutils/compiler.h> + +#include <SkUtils.h> + +#include "Debug.h" +#include "FontUtil.h" +#include "Font.h" +#include "FontRenderer.h" +#include "Properties.h" + +namespace android { +namespace uirenderer { + +/////////////////////////////////////////////////////////////////////////////// +// Font +/////////////////////////////////////////////////////////////////////////////// + +Font::Font(FontRenderer* state, uint32_t fontId, float fontSize, + int flags, uint32_t italicStyle, uint32_t scaleX, + SkPaint::Style style, uint32_t strokeWidth) : + mState(state), mFontId(fontId), mFontSize(fontSize), + mFlags(flags), mItalicStyle(italicStyle), mScaleX(scaleX), + mStyle(style), mStrokeWidth(mStrokeWidth) { +} + + +Font::~Font() { + for (uint32_t ct = 0; ct < mState->mActiveFonts.size(); ct++) { + if (mState->mActiveFonts[ct] == this) { + mState->mActiveFonts.removeAt(ct); + break; + } + } + + for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) { + delete mCachedGlyphs.valueAt(i); + } +} + +void Font::invalidateTextureCache(CacheTexture *cacheTexture) { + for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) { + CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueAt(i); + if (cacheTexture == NULL || cachedGlyph->mCacheTexture == cacheTexture) { + cachedGlyph->mIsValid = false; + } + } +} + +void Font::measureCachedGlyph(CachedGlyphInfo *glyph, int x, int y, + uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) { + int nPenX = x + glyph->mBitmapLeft; + int nPenY = y + glyph->mBitmapTop; + + int width = (int) glyph->mBitmapWidth; + int height = (int) glyph->mBitmapHeight; + + if (bounds->bottom > nPenY) { + bounds->bottom = nPenY; + } + if (bounds->left > nPenX) { + bounds->left = nPenX; + } + if (bounds->right < nPenX + width) { + bounds->right = nPenX + width; + } + if (bounds->top < nPenY + height) { + bounds->top = nPenY + height; + } +} + +void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y, + uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) { + int nPenX = x + glyph->mBitmapLeft; + int nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight; + + float u1 = glyph->mBitmapMinU; + float u2 = glyph->mBitmapMaxU; + float v1 = glyph->mBitmapMinV; + float v2 = glyph->mBitmapMaxV; + + int width = (int) glyph->mBitmapWidth; + int height = (int) glyph->mBitmapHeight; + + mState->appendMeshQuad(nPenX, nPenY, u1, v2, + nPenX + width, nPenY, u2, v2, + nPenX + width, nPenY - height, u2, v1, + nPenX, nPenY - height, u1, v1, glyph->mCacheTexture); +} + +void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y, + uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) { + int nPenX = x + glyph->mBitmapLeft; + int nPenY = y + glyph->mBitmapTop; + + uint32_t endX = glyph->mStartX + glyph->mBitmapWidth; + uint32_t endY = glyph->mStartY + glyph->mBitmapHeight; + + CacheTexture *cacheTexture = glyph->mCacheTexture; + uint32_t cacheWidth = cacheTexture->mWidth; + const uint8_t* cacheBuffer = cacheTexture->mTexture; + + uint32_t cacheX = 0, cacheY = 0; + int32_t bX = 0, bY = 0; + for (cacheX = glyph->mStartX, bX = nPenX; cacheX < endX; cacheX++, bX++) { + for (cacheY = glyph->mStartY, bY = nPenY; cacheY < endY; cacheY++, bY++) { +#if DEBUG_FONT_RENDERER + if (bX < 0 || bY < 0 || bX >= (int32_t) bitmapW || bY >= (int32_t) bitmapH) { + ALOGE("Skipping invalid index"); + continue; + } +#endif + uint8_t tempCol = cacheBuffer[cacheY * cacheWidth + cacheX]; + bitmap[bY * bitmapW + bX] = tempCol; + } + } +} + +void Font::drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float vOffset, + SkPathMeasure& measure, SkPoint* position, SkVector* tangent) { + const float halfWidth = glyph->mBitmapWidth * 0.5f; + const float height = glyph->mBitmapHeight; + + vOffset += glyph->mBitmapTop + height; + + SkPoint destination[4]; + measure.getPosTan(x + hOffset + glyph->mBitmapLeft + halfWidth, position, tangent); + + // Move along the tangent and offset by the normal + destination[0].set(-tangent->fX * halfWidth - tangent->fY * vOffset, + -tangent->fY * halfWidth + tangent->fX * vOffset); + destination[1].set(tangent->fX * halfWidth - tangent->fY * vOffset, + tangent->fY * halfWidth + tangent->fX * vOffset); + destination[2].set(destination[1].fX + tangent->fY * height, + destination[1].fY - tangent->fX * height); + destination[3].set(destination[0].fX + tangent->fY * height, + destination[0].fY - tangent->fX * height); + + const float u1 = glyph->mBitmapMinU; + const float u2 = glyph->mBitmapMaxU; + const float v1 = glyph->mBitmapMinV; + const float v2 = glyph->mBitmapMaxV; + + mState->appendRotatedMeshQuad( + position->fX + destination[0].fX, + position->fY + destination[0].fY, u1, v2, + position->fX + destination[1].fX, + position->fY + destination[1].fY, u2, v2, + position->fX + destination[2].fX, + position->fY + destination[2].fY, u2, v1, + position->fX + destination[3].fX, + position->fY + destination[3].fY, u1, v1, + glyph->mCacheTexture); +} + +CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit, bool precaching) { + CachedGlyphInfo* cachedGlyph = NULL; + ssize_t index = mCachedGlyphs.indexOfKey(textUnit); + if (index >= 0) { + cachedGlyph = mCachedGlyphs.valueAt(index); + } else { + cachedGlyph = cacheGlyph(paint, textUnit, precaching); + } + + // Is the glyph still in texture cache? + if (!cachedGlyph->mIsValid) { + const SkGlyph& skiaGlyph = GET_METRICS(paint, textUnit); + updateGlyphCache(paint, skiaGlyph, cachedGlyph, precaching); + } + + return cachedGlyph; +} + +void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len, + int numGlyphs, int x, int y, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) { + if (bitmap != NULL && bitmapW > 0 && bitmapH > 0) { + render(paint, text, start, len, numGlyphs, x, y, BITMAP, bitmap, + bitmapW, bitmapH, NULL, NULL); + } else { + render(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, NULL, + 0, 0, NULL, NULL); + } +} + +void Font::render(SkPaint* paint, const char *text, uint32_t start, uint32_t len, + int numGlyphs, int x, int y, const float* positions) { + render(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, NULL, + 0, 0, NULL, positions); +} + +void Font::render(SkPaint* paint, const char *text, uint32_t start, uint32_t len, + int numGlyphs, SkPath* path, float hOffset, float vOffset) { + if (numGlyphs == 0 || text == NULL || len == 0) { + return; + } + + text += start; + + int glyphsCount = 0; + SkFixed prevRsbDelta = 0; + + float penX = 0.0f; + + SkPoint position; + SkVector tangent; + + SkPathMeasure measure(*path, false); + float pathLength = SkScalarToFloat(measure.getLength()); + + if (paint->getTextAlign() != SkPaint::kLeft_Align) { + float textWidth = SkScalarToFloat(paint->measureText(text, len)); + float pathOffset = pathLength; + if (paint->getTextAlign() == SkPaint::kCenter_Align) { + textWidth *= 0.5f; + pathOffset *= 0.5f; + } + penX += pathOffset - textWidth; + } + + while (glyphsCount < numGlyphs && penX < pathLength) { + glyph_t glyph = GET_GLYPH(text); + + if (IS_END_OF_STRING(glyph)) { + break; + } + + CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph); + penX += SkFixedToFloat(AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta)); + prevRsbDelta = cachedGlyph->mRsbDelta; + + if (cachedGlyph->mIsValid) { + drawCachedGlyph(cachedGlyph, penX, hOffset, vOffset, measure, &position, &tangent); + } + + penX += SkFixedToFloat(cachedGlyph->mAdvanceX); + + glyphsCount++; + } +} + +void Font::measure(SkPaint* paint, const char* text, uint32_t start, uint32_t len, + int numGlyphs, Rect *bounds, const float* positions) { + if (bounds == NULL) { + ALOGE("No return rectangle provided to measure text"); + return; + } + bounds->set(1e6, -1e6, -1e6, 1e6); + render(paint, text, start, len, numGlyphs, 0, 0, MEASURE, NULL, 0, 0, bounds, positions); +} + +void Font::precache(SkPaint* paint, const char* text, int numGlyphs) { + + if (numGlyphs == 0 || text == NULL) { + return; + } + int glyphsCount = 0; + + while (glyphsCount < numGlyphs) { + glyph_t glyph = GET_GLYPH(text); + + // Reached the end of the string + if (IS_END_OF_STRING(glyph)) { + break; + } + + CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph, true); + + glyphsCount++; + } +} + +void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len, + int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap, + uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* positions) { + if (numGlyphs == 0 || text == NULL || len == 0) { + return; + } + + static RenderGlyph gRenderGlyph[] = { + &android::uirenderer::Font::drawCachedGlyph, + &android::uirenderer::Font::drawCachedGlyphBitmap, + &android::uirenderer::Font::measureCachedGlyph + }; + RenderGlyph render = gRenderGlyph[mode]; + + text += start; + int glyphsCount = 0; + + if (CC_LIKELY(positions == NULL)) { + SkFixed prevRsbDelta = 0; + + float penX = x + 0.5f; + int penY = y; + + while (glyphsCount < numGlyphs) { + glyph_t glyph = GET_GLYPH(text); + + // Reached the end of the string + if (IS_END_OF_STRING(glyph)) { + break; + } + + CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph); + penX += SkFixedToFloat(AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta)); + prevRsbDelta = cachedGlyph->mRsbDelta; + + // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage + if (cachedGlyph->mIsValid) { + (*this.*render)(cachedGlyph, (int) floorf(penX), penY, + bitmap, bitmapW, bitmapH, bounds, positions); + } + + penX += SkFixedToFloat(cachedGlyph->mAdvanceX); + + glyphsCount++; + } + } else { + const SkPaint::Align align = paint->getTextAlign(); + + // This is for renderPosText() + while (glyphsCount < numGlyphs) { + glyph_t glyph = GET_GLYPH(text); + + // Reached the end of the string + if (IS_END_OF_STRING(glyph)) { + break; + } + + CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph); + + // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage + if (cachedGlyph->mIsValid) { + int penX = x + positions[(glyphsCount << 1)]; + int penY = y + positions[(glyphsCount << 1) + 1]; + + switch (align) { + case SkPaint::kRight_Align: + penX -= SkFixedToFloat(cachedGlyph->mAdvanceX); + penY -= SkFixedToFloat(cachedGlyph->mAdvanceY); + break; + case SkPaint::kCenter_Align: + penX -= SkFixedToFloat(cachedGlyph->mAdvanceX >> 1); + penY -= SkFixedToFloat(cachedGlyph->mAdvanceY >> 1); + default: + break; + } + + (*this.*render)(cachedGlyph, penX, penY, + bitmap, bitmapW, bitmapH, bounds, positions); + } + + glyphsCount++; + } + } +} + +void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph, + bool precaching) { + glyph->mAdvanceX = skiaGlyph.fAdvanceX; + glyph->mAdvanceY = skiaGlyph.fAdvanceY; + glyph->mBitmapLeft = skiaGlyph.fLeft; + glyph->mBitmapTop = skiaGlyph.fTop; + glyph->mLsbDelta = skiaGlyph.fLsbDelta; + glyph->mRsbDelta = skiaGlyph.fRsbDelta; + + uint32_t startX = 0; + uint32_t startY = 0; + + // Get the bitmap for the glyph + paint->findImage(skiaGlyph); + mState->cacheBitmap(skiaGlyph, glyph, &startX, &startY, precaching); + + if (!glyph->mIsValid) { + return; + } + + uint32_t endX = startX + skiaGlyph.fWidth; + uint32_t endY = startY + skiaGlyph.fHeight; + + glyph->mStartX = startX; + glyph->mStartY = startY; + glyph->mBitmapWidth = skiaGlyph.fWidth; + glyph->mBitmapHeight = skiaGlyph.fHeight; + + uint32_t cacheWidth = glyph->mCacheTexture->mWidth; + uint32_t cacheHeight = glyph->mCacheTexture->mHeight; + + glyph->mBitmapMinU = startX / (float) cacheWidth; + glyph->mBitmapMinV = startY / (float) cacheHeight; + glyph->mBitmapMaxU = endX / (float) cacheWidth; + glyph->mBitmapMaxV = endY / (float) cacheHeight; + + mState->mUploadTexture = true; +} + +CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching) { + CachedGlyphInfo* newGlyph = new CachedGlyphInfo(); + mCachedGlyphs.add(glyph, newGlyph); + + const SkGlyph& skiaGlyph = GET_METRICS(paint, glyph); + newGlyph->mGlyphIndex = skiaGlyph.fID; + newGlyph->mIsValid = false; + + updateGlyphCache(paint, skiaGlyph, newGlyph, precaching); + + return newGlyph; +} + +Font* Font::create(FontRenderer* state, uint32_t fontId, float fontSize, + int flags, uint32_t italicStyle, uint32_t scaleX, + SkPaint::Style style, uint32_t strokeWidth) { + Vector<Font*> &activeFonts = state->mActiveFonts; + + for (uint32_t i = 0; i < activeFonts.size(); i++) { + Font* font = activeFonts[i]; + if (font->mFontId == fontId && font->mFontSize == fontSize && + font->mFlags == flags && font->mItalicStyle == italicStyle && + font->mScaleX == scaleX && font->mStyle == style && + (style == SkPaint::kFill_Style || font->mStrokeWidth == strokeWidth)) { + return font; + } + } + + Font* newFont = new Font(state, fontId, fontSize, flags, italicStyle, + scaleX, style, strokeWidth); + activeFonts.push(newFont); + return newFont; +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h new file mode 100644 index 0000000..d1b75bd --- /dev/null +++ b/libs/hwui/font/Font.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_FONT_H +#define ANDROID_HWUI_FONT_H + +#include <utils/KeyedVector.h> + +#include <SkScalerContext.h> +#include <SkPaint.h> +#include <SkPathMeasure.h> + +#include "CachedGlyphInfo.h" +#include "../Rect.h" + +namespace android { +namespace uirenderer { + +/////////////////////////////////////////////////////////////////////////////// +// Font +/////////////////////////////////////////////////////////////////////////////// + +class FontRenderer; + +/** + * Represents a font, defined by a Skia font id and a font size. A font is used + * to generate glyphs and cache them in the FontState. + */ +class Font { +public: + enum Style { + kFakeBold = 1 + }; + + ~Font(); + + /** + * Renders the specified string of text. + * If bitmap is specified, it will be used as the render target + */ + void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len, + int numGlyphs, int x, int y, uint8_t *bitmap = NULL, + uint32_t bitmapW = 0, uint32_t bitmapH = 0); + + void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len, + int numGlyphs, int x, int y, const float* positions); + + void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len, + int numGlyphs, SkPath* path, float hOffset, float vOffset); + + /** + * Creates a new font associated with the specified font state. + */ + static Font* create(FontRenderer* state, uint32_t fontId, float fontSize, + int flags, uint32_t italicStyle, uint32_t scaleX, SkPaint::Style style, + uint32_t strokeWidth); + +private: + friend class FontRenderer; + typedef void (Font::*RenderGlyph)(CachedGlyphInfo*, int, int, uint8_t*, + uint32_t, uint32_t, Rect*, const float*); + + enum RenderMode { + FRAMEBUFFER, + BITMAP, + MEASURE, + }; + + void precache(SkPaint* paint, const char* text, int numGlyphs); + + void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len, + int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap, + uint32_t bitmapW, uint32_t bitmapH, Rect *bounds, const float* positions); + + void measure(SkPaint* paint, const char* text, uint32_t start, uint32_t len, + int numGlyphs, Rect *bounds, const float* positions); + + Font(FontRenderer* state, uint32_t fontId, float fontSize, int flags, uint32_t italicStyle, + uint32_t scaleX, SkPaint::Style style, uint32_t strokeWidth); + + // Cache of glyphs + DefaultKeyedVector<glyph_t, CachedGlyphInfo*> mCachedGlyphs; + + void invalidateTextureCache(CacheTexture *cacheTexture = NULL); + + CachedGlyphInfo* cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching); + void updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph, + bool precaching); + + void measureCachedGlyph(CachedGlyphInfo* glyph, int x, int y, + uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH, + Rect* bounds, const float* pos); + void drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y, + uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH, + Rect* bounds, const float* pos); + void drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y, + uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH, + Rect* bounds, const float* pos); + void drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float vOffset, + SkPathMeasure& measure, SkPoint* position, SkVector* tangent); + + CachedGlyphInfo* getCachedGlyph(SkPaint* paint, glyph_t textUnit, bool precaching = false); + + FontRenderer* mState; + uint32_t mFontId; + float mFontSize; + int mFlags; + uint32_t mItalicStyle; + uint32_t mScaleX; + SkPaint::Style mStyle; + uint32_t mStrokeWidth; +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_FONT_H diff --git a/libs/hwui/font/FontUtil.h b/libs/hwui/font/FontUtil.h new file mode 100644 index 0000000..12247ba --- /dev/null +++ b/libs/hwui/font/FontUtil.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_FONT_UTIL_H +#define ANDROID_HWUI_FONT_UTIL_H + +#include <SkUtils.h> + +#include "Properties.h" + +/////////////////////////////////////////////////////////////////////////////// +// Defines +/////////////////////////////////////////////////////////////////////////////// + +#define DEFAULT_TEXT_SMALL_CACHE_WIDTH 1024 +#define DEFAULT_TEXT_SMALL_CACHE_HEIGHT 256 +#define DEFAULT_TEXT_LARGE_CACHE_WIDTH 2048 +#define DEFAULT_TEXT_LARGE_CACHE_HEIGHT 512 + +#define TEXTURE_BORDER_SIZE 1 + +#define CACHE_BLOCK_ROUNDING_SIZE 4 + +#if RENDER_TEXT_AS_GLYPHS + typedef uint16_t glyph_t; + #define TO_GLYPH(g) g + #define GET_METRICS(paint, glyph) paint->getGlyphMetrics(glyph) + #define GET_GLYPH(text) nextGlyph((const uint16_t**) &text) + #define IS_END_OF_STRING(glyph) false + + static glyph_t nextGlyph(const uint16_t** srcPtr) { + const uint16_t* src = *srcPtr; + glyph_t g = *src++; + *srcPtr = src; + return g; + } +#else + typedef SkUnichar glyph_t; + #define TO_GLYPH(g) ((SkUnichar) g) + #define GET_METRICS(paint, glyph) paint->getUnicharMetrics(glyph) + #define GET_GLYPH(text) SkUTF16_NextUnichar((const uint16_t**) &text) + #define IS_END_OF_STRING(glyph) glyph < 0 +#endif + +#define AUTO_KERN(prev, next) (((next) - (prev) + 32) >> 6 << 16) + +#endif // ANDROID_HWUI_FONT_UTIL_H |