| /* |
| * Copyright (C) 2011 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 "TextLayoutCache.h" |
| #include "TextLayout.h" |
| |
| namespace android { |
| |
| TextLayoutCache::TextLayoutCache() : |
| mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutCacheValue> >::kUnlimitedCapacity), |
| mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)), |
| mCacheHitCount(0), mNanosecondsSaved(0) { |
| init(); |
| } |
| |
| TextLayoutCache::TextLayoutCache(uint32_t max): |
| mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutCacheValue> >::kUnlimitedCapacity), |
| mSize(0), mMaxSize(max), |
| mCacheHitCount(0), mNanosecondsSaved(0) { |
| init(); |
| } |
| |
| TextLayoutCache::~TextLayoutCache() { |
| mCache.clear(); |
| } |
| |
| void TextLayoutCache::init() { |
| mCache.setOnEntryRemovedListener(this); |
| |
| mDebugLevel = readRtlDebugLevel(); |
| mDebugEnabled = mDebugLevel & kRtlDebugCaches; |
| LOGD("Using TextLayoutCache debug level: %d - Debug Enabled: %d", mDebugLevel, mDebugEnabled); |
| |
| mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC); |
| if (mDebugEnabled) { |
| LOGD("TextLayoutCache start time: %lld", mCacheStartTime); |
| } |
| mInitialized = true; |
| |
| if (mDebugEnabled) { |
| #if RTL_USE_HARFBUZZ |
| LOGD("TextLayoutCache is using HARFBUZZ"); |
| #else |
| LOGD("TextLayoutCache is using ICU"); |
| #endif |
| } |
| |
| if (mDebugEnabled) { |
| LOGD("TextLayoutCache initialization is done"); |
| } |
| } |
| |
| /* |
| * Size management |
| */ |
| |
| uint32_t TextLayoutCache::getSize() { |
| return mSize; |
| } |
| |
| uint32_t TextLayoutCache::getMaxSize() { |
| return mMaxSize; |
| } |
| |
| void TextLayoutCache::setMaxSize(uint32_t maxSize) { |
| mMaxSize = maxSize; |
| removeOldests(); |
| } |
| |
| void TextLayoutCache::removeOldests() { |
| while (mSize > mMaxSize) { |
| mCache.removeOldest(); |
| } |
| } |
| |
| /** |
| * Callbacks |
| */ |
| void TextLayoutCache::operator()(TextLayoutCacheKey& text, sp<TextLayoutCacheValue>& desc) { |
| if (desc != NULL) { |
| size_t totalSizeToDelete = text.getSize() + desc->getSize(); |
| mSize -= totalSizeToDelete; |
| if (mDebugEnabled) { |
| LOGD("Cache value deleted, size = %d", totalSizeToDelete); |
| } |
| desc.clear(); |
| } |
| } |
| |
| /* |
| * Cache clearing |
| */ |
| void TextLayoutCache::clear() { |
| mCache.clear(); |
| } |
| |
| /* |
| * Caching |
| */ |
| sp<TextLayoutCacheValue> TextLayoutCache::getValue(SkPaint* paint, |
| const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) { |
| AutoMutex _l(mLock); |
| nsecs_t startTime = 0; |
| if (mDebugEnabled) { |
| startTime = systemTime(SYSTEM_TIME_MONOTONIC); |
| } |
| |
| // Create the key |
| TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags); |
| |
| // Get value from cache if possible |
| sp<TextLayoutCacheValue> value = mCache.get(key); |
| |
| // Value not found for the key, we need to add a new value in the cache |
| if (value == NULL) { |
| if (mDebugEnabled) { |
| startTime = systemTime(SYSTEM_TIME_MONOTONIC); |
| } |
| |
| value = new TextLayoutCacheValue(); |
| |
| // Compute advances and store them |
| value->computeValues(paint, text, start, count, contextCount, dirFlags); |
| |
| nsecs_t endTime = systemTime(SYSTEM_TIME_MONOTONIC); |
| |
| // Don't bother to add in the cache if the entry is too big |
| size_t size = key.getSize() + value->getSize(); |
| if (size <= mMaxSize) { |
| // Cleanup to make some room if needed |
| if (mSize + size > mMaxSize) { |
| if (mDebugEnabled) { |
| LOGD("TextLayoutCache: need to clean some entries " |
| "for making some room for a new entry"); |
| } |
| while (mSize + size > mMaxSize) { |
| // This will call the callback |
| mCache.removeOldest(); |
| } |
| } |
| |
| // Update current cache size |
| mSize += size; |
| |
| // Copy the text when we insert the new entry |
| key.internalTextCopy(); |
| mCache.put(key, value); |
| |
| if (mDebugEnabled) { |
| // Update timing information for statistics |
| value->setElapsedTime(endTime - startTime); |
| |
| LOGD("CACHE MISS: Added entry for text='%s' with start=%d, count=%d, " |
| "contextCount=%d, entry size %d bytes, remaining space %d bytes" |
| " - Compute time in nanos: %d", |
| String8(text, contextCount).string(), start, count, contextCount, |
| size, mMaxSize - mSize, value->getElapsedTime()); |
| } |
| } else { |
| if (mDebugEnabled) { |
| LOGD("CACHE MISS: Calculated but not storing entry because it is too big " |
| "for text='%s' with start=%d, count=%d, contextCount=%d, " |
| "entry size %d bytes, remaining space %d bytes" |
| " - Compute time in nanos: %lld", |
| String8(text, contextCount).string(), start, count, contextCount, |
| size, mMaxSize - mSize, endTime); |
| } |
| value.clear(); |
| } |
| } else { |
| // This is a cache hit, just log timestamp and user infos |
| if (mDebugEnabled) { |
| nsecs_t elapsedTimeThruCacheGet = systemTime(SYSTEM_TIME_MONOTONIC) - startTime; |
| mNanosecondsSaved += (value->getElapsedTime() - elapsedTimeThruCacheGet); |
| ++mCacheHitCount; |
| |
| if (value->getElapsedTime() > 0) { |
| float deltaPercent = 100 * ((value->getElapsedTime() - elapsedTimeThruCacheGet) |
| / ((float)value->getElapsedTime())); |
| LOGD("CACHE HIT #%d for text='%s' with start=%d, count=%d, contextCount=%d " |
| "- Compute time in nanos: %d - " |
| "Cache get time in nanos: %lld - Gain in percent: %2.2f", |
| mCacheHitCount, String8(text, contextCount).string(), start, count, |
| contextCount, |
| value->getElapsedTime(), elapsedTimeThruCacheGet, deltaPercent); |
| } |
| if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) { |
| dumpCacheStats(); |
| } |
| } |
| } |
| return value; |
| } |
| |
| void TextLayoutCache::dumpCacheStats() { |
| float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize)); |
| float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000; |
| LOGD("------------------------------------------------"); |
| LOGD("TextLayoutCache stats"); |
| LOGD("------------------------------------------------"); |
| LOGD("pid : %d", getpid()); |
| LOGD("running : %.0f seconds", timeRunningInSec); |
| LOGD("entries : %d", mCache.size()); |
| LOGD("size : %d bytes", mMaxSize); |
| LOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent); |
| LOGD("hits : %d", mCacheHitCount); |
| LOGD("saved : %lld milliseconds", mNanosecondsSaved / 1000000); |
| LOGD("------------------------------------------------"); |
| } |
| |
| /** |
| * TextLayoutCacheKey |
| */ |
| TextLayoutCacheKey::TextLayoutCacheKey(): text(NULL), start(0), count(0), contextCount(0), |
| dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0), |
| hinting(SkPaint::kNo_Hinting) { |
| } |
| |
| TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, |
| const UChar* text, size_t start, size_t count, |
| size_t contextCount, int dirFlags) : |
| text(text), start(start), count(count), contextCount(contextCount), |
| dirFlags(dirFlags) { |
| typeface = paint->getTypeface(); |
| textSize = paint->getTextSize(); |
| textSkewX = paint->getTextSkewX(); |
| textScaleX = paint->getTextScaleX(); |
| flags = paint->getFlags(); |
| hinting = paint->getHinting(); |
| } |
| |
| bool TextLayoutCacheKey::operator<(const TextLayoutCacheKey& rhs) const { |
| LTE_INT(count) { |
| LTE_INT(contextCount) { |
| LTE_INT(start) { |
| LTE_INT(typeface) { |
| LTE_FLOAT(textSize) { |
| LTE_FLOAT(textSkewX) { |
| LTE_FLOAT(textScaleX) { |
| LTE_INT(flags) { |
| LTE_INT(hinting) { |
| LTE_INT(dirFlags) { |
| return strncmp16(text, rhs.text, contextCount) < 0; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| void TextLayoutCacheKey::internalTextCopy() { |
| textCopy.setTo(text, contextCount); |
| text = textCopy.string(); |
| } |
| |
| size_t TextLayoutCacheKey::getSize() { |
| return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount; |
| } |
| |
| /** |
| * TextLayoutCacheValue |
| */ |
| TextLayoutCacheValue::TextLayoutCacheValue() : |
| mAdvances(NULL), mTotalAdvance(0), mAdvancesCount(0), |
| mGlyphs(NULL), mGlyphsCount(0), mElapsedTime(0) { |
| } |
| |
| TextLayoutCacheValue::~TextLayoutCacheValue() { |
| delete[] mAdvances; |
| delete[] mGlyphs; |
| } |
| |
| void TextLayoutCacheValue::setElapsedTime(uint32_t time) { |
| mElapsedTime = time; |
| } |
| |
| uint32_t TextLayoutCacheValue::getElapsedTime() { |
| return mElapsedTime; |
| } |
| |
| void TextLayoutCacheValue::computeValues(SkPaint* paint, const UChar* chars, size_t start, |
| size_t count, size_t contextCount, int dirFlags) { |
| mAdvancesCount = count; |
| mAdvances = new float[count]; |
| |
| #if RTL_USE_HARFBUZZ |
| computeValuesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags, |
| mAdvances, &mTotalAdvance, &mGlyphs, &mGlyphsCount); |
| #else |
| computeAdvancesWithICU(paint, chars, start, count, contextCount, dirFlags, |
| mAdvances, &mTotalAdvance); |
| #endif |
| #if DEBUG_ADVANCES |
| LOGD("Advances - count=%d - countextCount=%d - totalAdvance=%f - " |
| "adv[0]=%f adv[1]=%f adv[2]=%f adv[3]=%f", count, contextCount, mTotalAdvance, |
| mAdvances[0], mAdvances[1], mAdvances[2], mAdvances[3]); |
| #endif |
| } |
| |
| size_t TextLayoutCacheValue::getSize() { |
| return sizeof(TextLayoutCacheValue) + sizeof(jfloat) * mAdvancesCount + |
| sizeof(jchar) * mGlyphsCount; |
| } |
| |
| void TextLayoutCacheValue::setupShaperItem(HB_ShaperItem* shaperItem, HB_FontRec* font, |
| FontData* fontData, SkPaint* paint, const UChar* chars, size_t start, size_t count, |
| size_t contextCount, int dirFlags) { |
| bool isRTL = dirFlags & 0x1; |
| |
| font->klass = &harfbuzzSkiaClass; |
| font->userData = 0; |
| // The values which harfbuzzSkiaClass returns are already scaled to |
| // pixel units, so we just set all these to one to disable further |
| // scaling. |
| font->x_ppem = 1; |
| font->y_ppem = 1; |
| font->x_scale = 1; |
| font->y_scale = 1; |
| |
| memset(shaperItem, 0, sizeof(*shaperItem)); |
| shaperItem->font = font; |
| shaperItem->face = HB_NewFace(shaperItem->font, harfbuzzSkiaGetTable); |
| |
| shaperItem->kerning_applied = false; |
| |
| // We cannot know, ahead of time, how many glyphs a given script run |
| // will produce. We take a guess that script runs will not produce more |
| // than twice as many glyphs as there are code points plus a bit of |
| // padding and fallback if we find that we are wrong. |
| createGlyphArrays(shaperItem, (contextCount + 2) * 2); |
| |
| // Free memory for clusters if needed and recreate the clusters array |
| if (shaperItem->log_clusters) { |
| delete shaperItem->log_clusters; |
| } |
| shaperItem->log_clusters = new unsigned short[contextCount]; |
| |
| shaperItem->item.pos = start; |
| shaperItem->item.length = count; |
| shaperItem->item.bidiLevel = isRTL; |
| shaperItem->item.script = isRTL ? HB_Script_Arabic : HB_Script_Common; |
| |
| shaperItem->string = chars; |
| shaperItem->stringLength = contextCount; |
| |
| fontData->typeFace = paint->getTypeface(); |
| fontData->textSize = paint->getTextSize(); |
| fontData->textSkewX = paint->getTextSkewX(); |
| fontData->textScaleX = paint->getTextScaleX(); |
| fontData->flags = paint->getFlags(); |
| fontData->hinting = paint->getHinting(); |
| |
| shaperItem->font->userData = fontData; |
| } |
| |
| void TextLayoutCacheValue::shapeWithHarfbuzz(HB_ShaperItem* shaperItem, HB_FontRec* font, |
| FontData* fontData, SkPaint* paint, const UChar* chars, size_t start, size_t count, |
| size_t contextCount, int dirFlags) { |
| // Setup Harfbuzz Shaper |
| setupShaperItem(shaperItem, font, fontData, paint, chars, start, count, |
| contextCount, dirFlags); |
| |
| // Shape |
| resetGlyphArrays(shaperItem); |
| while (!HB_ShapeItem(shaperItem)) { |
| // We overflowed our arrays. Resize and retry. |
| // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size. |
| deleteGlyphArrays(shaperItem); |
| createGlyphArrays(shaperItem, shaperItem->num_glyphs << 1); |
| resetGlyphArrays(shaperItem); |
| } |
| } |
| |
| struct GlyphRun { |
| inline GlyphRun() {} |
| inline GlyphRun(jchar* glyphs, size_t glyphsCount, bool isRTL) : |
| glyphs(glyphs), glyphsCount(glyphsCount), isRTL(isRTL) { } |
| jchar* glyphs; |
| size_t glyphsCount; |
| int isRTL; |
| }; |
| |
| void static reverseGlyphArray(jchar* glyphs, size_t count) { |
| for (size_t i = 0; i < count / 2; i++) { |
| jchar temp = glyphs[i]; |
| glyphs[i] = glyphs[count - 1 - i]; |
| glyphs[count - 1 - i] = temp; |
| } |
| } |
| |
| void TextLayoutCacheValue::computeValuesWithHarfbuzz(SkPaint* paint, const UChar* chars, |
| size_t start, size_t count, size_t contextCount, int dirFlags, |
| jfloat* outAdvances, jfloat* outTotalAdvance, |
| jchar** outGlyphs, size_t* outGlyphsCount) { |
| |
| UBiDiLevel bidiReq = 0; |
| bool forceLTR = false; |
| bool forceRTL = false; |
| |
| switch (dirFlags) { |
| case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level |
| case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level |
| case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break; |
| case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break; |
| case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR |
| case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL |
| } |
| |
| if (forceLTR || forceRTL) { |
| #if DEBUG_GLYPHS |
| LOGD("computeValuesWithHarfbuzz -- forcing run with LTR=%d RTL=%d", |
| forceLTR, forceRTL); |
| #endif |
| computeRunValuesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags, |
| outAdvances, outTotalAdvance, outGlyphs, outGlyphsCount); |
| |
| if (forceRTL && *outGlyphsCount > 1) { |
| reverseGlyphArray(*outGlyphs, *outGlyphsCount); |
| } |
| } else { |
| UBiDi* bidi = ubidi_open(); |
| if (bidi) { |
| UErrorCode status = U_ZERO_ERROR; |
| #if DEBUG_GLYPHS |
| LOGD("computeValuesWithHarfbuzz -- bidiReq=%d", bidiReq); |
| #endif |
| ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status); |
| if (U_SUCCESS(status)) { |
| int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl |
| size_t rc = ubidi_countRuns(bidi, &status); |
| #if DEBUG_GLYPHS |
| LOGD("computeValuesWithHarfbuzz -- dirFlags=%d run-count=%d paraDir=%d", dirFlags, rc, paraDir); |
| #endif |
| if (rc == 1 || !U_SUCCESS(status)) { |
| computeRunValuesWithHarfbuzz(paint, chars, start, count, contextCount, |
| dirFlags, outAdvances, outTotalAdvance, outGlyphs, outGlyphsCount); |
| |
| if (dirFlags == 1 && *outGlyphsCount > 1) { |
| reverseGlyphArray(*outGlyphs, *outGlyphsCount); |
| } |
| } else { |
| Vector<GlyphRun> glyphRuns; |
| jchar* runGlyphs; |
| size_t runGlyphsCount = 0; |
| size_t runIndex = 0; |
| for (size_t i = 0; i < rc; ++i) { |
| int32_t startRun; |
| int32_t lengthRun; |
| UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun); |
| |
| int newFlags = (runDir == UBIDI_RTL) ? kDirection_RTL : kDirection_LTR; |
| jfloat runTotalAdvance = 0; |
| #if DEBUG_GLYPHS |
| LOGD("computeValuesWithHarfbuzz -- run-start=%d run-len=%d newFlags=%d", |
| startRun, lengthRun, newFlags); |
| #endif |
| computeRunValuesWithHarfbuzz(paint, chars, startRun, |
| lengthRun, contextCount, newFlags, |
| outAdvances + runIndex, &runTotalAdvance, |
| &runGlyphs, &runGlyphsCount); |
| |
| runIndex += lengthRun; |
| |
| *outTotalAdvance += runTotalAdvance; |
| *outGlyphsCount += runGlyphsCount; |
| #if DEBUG_GLYPHS |
| LOGD("computeValuesWithHarfbuzz -- run=%d run-glyphs-count=%d", |
| i, runGlyphsCount); |
| for (size_t j = 0; j < runGlyphsCount; j++) { |
| LOGD(" -- glyphs[%d]=%d", j, runGlyphs[j]); |
| } |
| #endif |
| glyphRuns.push(GlyphRun(runGlyphs, runGlyphsCount, newFlags)); |
| } |
| *outGlyphs = new jchar[*outGlyphsCount]; |
| |
| jchar* glyphs = *outGlyphs; |
| for (size_t i = 0; i < glyphRuns.size(); i++) { |
| const GlyphRun& glyphRun = glyphRuns.itemAt(i); |
| if (glyphRun.isRTL) { |
| for (size_t n = 0; n < glyphRun.glyphsCount; n++) { |
| glyphs[glyphRun.glyphsCount - n - 1] = glyphRun.glyphs[n]; |
| } |
| } else { |
| memcpy(glyphs, glyphRun.glyphs, glyphRun.glyphsCount * sizeof(jchar)); |
| } |
| glyphs += glyphRun.glyphsCount; |
| delete[] glyphRun.glyphs; |
| } |
| } |
| } |
| ubidi_close(bidi); |
| } else { |
| // Cannot run BiDi, just consider one Run |
| #if DEBUG_GLYPHS |
| LOGD("computeValuesWithHarfbuzz -- cannot run BiDi, considering only one Run"); |
| #endif |
| computeRunValuesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags, |
| outAdvances, outTotalAdvance, outGlyphs, outGlyphsCount); |
| |
| if (dirFlags == 1 && *outGlyphsCount > 1) { |
| reverseGlyphArray(*outGlyphs, *outGlyphsCount); |
| } |
| } |
| } |
| #if DEBUG_GLYPHS |
| LOGD("computeValuesWithHarfbuzz -- total-glyphs-count=%d", *outGlyphsCount); |
| #endif |
| } |
| |
| void TextLayoutCacheValue::computeRunValuesWithHarfbuzz(SkPaint* paint, const UChar* chars, |
| size_t start, size_t count, size_t contextCount, int dirFlags, |
| jfloat* outAdvances, jfloat* outTotalAdvance, |
| jchar** outGlyphs, size_t* outGlyphsCount) { |
| |
| bool isRTL = dirFlags & 0x1; |
| |
| HB_ShaperItem shaperItem; |
| HB_FontRec font; |
| FontData fontData; |
| shapeWithHarfbuzz(&shaperItem, &font, &fontData, paint, chars, start, count, |
| contextCount, dirFlags); |
| |
| #if DEBUG_GLYPHS |
| LOGD("HARFBUZZ -- num_glypth=%d - kerning_applied=%d", shaperItem.num_glyphs, |
| shaperItem.kerning_applied); |
| LOGD(" -- string= '%s'", String8(chars + start, count).string()); |
| LOGD(" -- isDevKernText=%d", paint->isDevKernText()); |
| #endif |
| |
| if (shaperItem.advances == NULL || shaperItem.num_glyphs == 0) { |
| #if DEBUG_GLYPHS |
| LOGD("HARFBUZZ -- advances array is empty or num_glypth = 0"); |
| #endif |
| for (size_t i = 0; i < count; i++) { |
| outAdvances[i] = 0; |
| } |
| *outTotalAdvance = 0; |
| |
| if (outGlyphs) { |
| *outGlyphsCount = 0; |
| *outGlyphs = new jchar[0]; |
| } |
| |
| // Cleaning |
| deleteGlyphArrays(&shaperItem); |
| HB_FreeFace(shaperItem.face); |
| return; |
| } |
| // Get Advances and their total |
| jfloat totalAdvance = outAdvances[0] = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[0]]); |
| for (size_t i = 1; i < count; i++) { |
| size_t clusterPrevious = shaperItem.log_clusters[i - 1]; |
| size_t cluster = shaperItem.log_clusters[i]; |
| if (cluster == clusterPrevious) { |
| outAdvances[i] = 0; |
| } else { |
| totalAdvance += outAdvances[i] = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[i]]); |
| } |
| } |
| *outTotalAdvance = totalAdvance; |
| |
| #if DEBUG_ADVANCES |
| for (size_t i = 0; i < count; i++) { |
| LOGD("hb-adv[%d] = %f - log_clusters = %d - total = %f", i, |
| outAdvances[i], shaperItem.log_clusters[i], totalAdvance); |
| } |
| #endif |
| |
| // Get Glyphs |
| if (outGlyphs) { |
| *outGlyphsCount = shaperItem.num_glyphs; |
| *outGlyphs = new jchar[shaperItem.num_glyphs]; |
| for (size_t i = 0; i < shaperItem.num_glyphs; i++) { |
| (*outGlyphs)[i] = (jchar) shaperItem.glyphs[i]; |
| } |
| } |
| |
| // Cleaning |
| deleteGlyphArrays(&shaperItem); |
| HB_FreeFace(shaperItem.face); |
| } |
| |
| void TextLayoutCacheValue::computeAdvancesWithICU(SkPaint* paint, const UChar* chars, |
| size_t start, size_t count, size_t contextCount, int dirFlags, |
| jfloat* outAdvances, jfloat* outTotalAdvance) { |
| SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount); |
| jchar* buffer = tempBuffer.get(); |
| SkScalar* scalarArray = (SkScalar*)outAdvances; |
| |
| // this is where we'd call harfbuzz |
| // for now we just use ushape.c |
| size_t widths; |
| const jchar* text; |
| if (dirFlags & 0x1) { // rtl, call arabic shaping in case |
| UErrorCode status = U_ZERO_ERROR; |
| // Use fixed length since we need to keep start and count valid |
| u_shapeArabic(chars, contextCount, buffer, contextCount, |
| U_SHAPE_LENGTH_FIXED_SPACES_NEAR | |
| U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE | |
| U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status); |
| // we shouldn't fail unless there's an out of memory condition, |
| // in which case we're hosed anyway |
| for (int i = start, e = i + count; i < e; ++i) { |
| if (buffer[i] == UNICODE_NOT_A_CHAR) { |
| buffer[i] = UNICODE_ZWSP; // zero-width-space for skia |
| } |
| } |
| text = buffer + start; |
| widths = paint->getTextWidths(text, count << 1, scalarArray); |
| } else { |
| text = chars + start; |
| widths = paint->getTextWidths(text, count << 1, scalarArray); |
| } |
| |
| jfloat totalAdvance = 0; |
| if (widths < count) { |
| #if DEBUG_ADVANCES |
| LOGD("ICU -- count=%d", widths); |
| #endif |
| // Skia operates on code points, not code units, so surrogate pairs return only |
| // one value. Expand the result so we have one value per UTF-16 code unit. |
| |
| // Note, skia's getTextWidth gets confused if it encounters a surrogate pair, |
| // leaving the remaining widths zero. Not nice. |
| for (size_t i = 0, p = 0; i < widths; ++i) { |
| totalAdvance += outAdvances[p++] = SkScalarToFloat(scalarArray[i]); |
| if (p < count && |
| text[p] >= UNICODE_FIRST_LOW_SURROGATE && |
| text[p] < UNICODE_FIRST_PRIVATE_USE && |
| text[p-1] >= UNICODE_FIRST_HIGH_SURROGATE && |
| text[p-1] < UNICODE_FIRST_LOW_SURROGATE) { |
| outAdvances[p++] = 0; |
| } |
| #if DEBUG_ADVANCES |
| LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance); |
| #endif |
| } |
| } else { |
| #if DEBUG_ADVANCES |
| LOGD("ICU -- count=%d", count); |
| #endif |
| for (size_t i = 0; i < count; i++) { |
| totalAdvance += outAdvances[i] = SkScalarToFloat(scalarArray[i]); |
| #if DEBUG_ADVANCES |
| LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance); |
| #endif |
| } |
| } |
| *outTotalAdvance = totalAdvance; |
| } |
| |
| void TextLayoutCacheValue::deleteGlyphArrays(HB_ShaperItem* shaperItem) { |
| delete[] shaperItem->glyphs; |
| delete[] shaperItem->attributes; |
| delete[] shaperItem->advances; |
| delete[] shaperItem->offsets; |
| } |
| |
| void TextLayoutCacheValue::createGlyphArrays(HB_ShaperItem* shaperItem, int size) { |
| shaperItem->glyphs = new HB_Glyph[size]; |
| shaperItem->attributes = new HB_GlyphAttributes[size]; |
| shaperItem->advances = new HB_Fixed[size]; |
| shaperItem->offsets = new HB_FixedPoint[size]; |
| shaperItem->num_glyphs = size; |
| } |
| |
| void TextLayoutCacheValue::resetGlyphArrays(HB_ShaperItem* shaperItem) { |
| int size = shaperItem->num_glyphs; |
| // All the types here don't have pointers. It is safe to reset to |
| // zero unless Harfbuzz breaks the compatibility in the future. |
| memset(shaperItem->glyphs, 0, size * sizeof(shaperItem->glyphs[0])); |
| memset(shaperItem->attributes, 0, size * sizeof(shaperItem->attributes[0])); |
| memset(shaperItem->advances, 0, size * sizeof(shaperItem->advances[0])); |
| memset(shaperItem->offsets, 0, size * sizeof(shaperItem->offsets[0])); |
| } |
| |
| |
| } // namespace android |