2 * Copyright (C) 2011 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #define LOG_TAG "TextLayoutCache"
19 #include <utils/JenkinsHash.h>
21 #include "TextLayoutCache.h"
22 #include "TextLayout.h"
23 #include "SkFontHost.h"
24 #include "SkTypeface_android.h"
25 #include "HarfBuzzNGFaceSkia.h"
26 #include <unicode/unistr.h>
27 #include <unicode/uchar.h>
32 //--------------------------------------------------------------------------------------------------
34 ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutEngine);
36 //--------------------------------------------------------------------------------------------------
38 TextLayoutCache::TextLayoutCache(TextLayoutShaper* shaper) :
40 mCache(LruCache<TextLayoutCacheKey, sp<TextLayoutValue> >::kUnlimitedCapacity),
41 mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)),
42 mCacheHitCount(0), mNanosecondsSaved(0) {
46 TextLayoutCache::~TextLayoutCache() {
50 void TextLayoutCache::init() {
51 mCache.setOnEntryRemovedListener(this);
53 mDebugLevel = readRtlDebugLevel();
54 mDebugEnabled = mDebugLevel & kRtlDebugCaches;
55 ALOGD("Using debug level = %d - Debug Enabled = %d", mDebugLevel, mDebugEnabled);
57 mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
60 ALOGD("Initialization is done - Start time = %lld", mCacheStartTime);
69 void TextLayoutCache::operator()(TextLayoutCacheKey& text, sp<TextLayoutValue>& desc) {
70 size_t totalSizeToDelete = text.getSize() + desc->getSize();
71 mSize -= totalSizeToDelete;
73 ALOGD("Cache value %p deleted, size = %d", desc.get(), totalSizeToDelete);
80 void TextLayoutCache::purgeCaches() {
83 mShaper->purgeCaches();
89 sp<TextLayoutValue> TextLayoutCache::getValue(const SkPaint* paint,
90 const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) {
92 nsecs_t startTime = 0;
94 startTime = systemTime(SYSTEM_TIME_MONOTONIC);
98 TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags);
100 // Get value from cache if possible
101 sp<TextLayoutValue> value = mCache.get(key);
103 // Value not found for the key, we need to add a new value in the cache
106 startTime = systemTime(SYSTEM_TIME_MONOTONIC);
109 value = new TextLayoutValue(contextCount);
111 // Compute advances and store them
112 mShaper->computeValues(value.get(), paint,
113 reinterpret_cast<const UChar*>(key.getText()), start, count,
114 size_t(contextCount), int(dirFlags));
117 value->setElapsedTime(systemTime(SYSTEM_TIME_MONOTONIC) - startTime);
120 // Don't bother to add in the cache if the entry is too big
121 size_t size = key.getSize() + value->getSize();
122 if (size <= mMaxSize) {
123 // Cleanup to make some room if needed
124 if (mSize + size > mMaxSize) {
126 ALOGD("Need to clean some entries for making some room for a new entry");
128 while (mSize + size > mMaxSize) {
129 // This will call the callback
130 bool removedOne = mCache.removeOldest();
131 LOG_ALWAYS_FATAL_IF(!removedOne, "The cache is non-empty but we "
132 "failed to remove the oldest entry. "
133 "mSize = %u, size = %u, mMaxSize = %u, mCache.size() = %u",
134 mSize, size, mMaxSize, mCache.size());
138 // Update current cache size
141 bool putOne = mCache.put(key, value);
142 LOG_ALWAYS_FATAL_IF(!putOne, "Failed to put an entry into the cache. "
143 "This indicates that the cache already has an entry with the "
144 "same key but it should not since we checked earlier!"
145 " - start = %d, count = %d, contextCount = %d - Text = '%s'",
146 start, count, contextCount, String8(key.getText() + start, count).string());
149 nsecs_t totalTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
150 ALOGD("CACHE MISS: Added entry %p "
151 "with start = %d, count = %d, contextCount = %d, "
152 "entry size %d bytes, remaining space %d bytes"
153 " - Compute time %0.6f ms - Put time %0.6f ms - Text = '%s'",
154 value.get(), start, count, contextCount, size, mMaxSize - mSize,
155 value->getElapsedTime() * 0.000001f,
156 (totalTime - value->getElapsedTime()) * 0.000001f,
157 String8(key.getText() + start, count).string());
161 ALOGD("CACHE MISS: Calculated but not storing entry because it is too big "
162 "with start = %d, count = %d, contextCount = %d, "
163 "entry size %d bytes, remaining space %d bytes"
164 " - Compute time %0.6f ms - Text = '%s'",
165 start, count, contextCount, size, mMaxSize - mSize,
166 value->getElapsedTime() * 0.000001f,
167 String8(key.getText() + start, count).string());
171 // This is a cache hit, just log timestamp and user infos
173 nsecs_t elapsedTimeThruCacheGet = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
174 mNanosecondsSaved += (value->getElapsedTime() - elapsedTimeThruCacheGet);
177 if (value->getElapsedTime() > 0) {
178 float deltaPercent = 100 * ((value->getElapsedTime() - elapsedTimeThruCacheGet)
179 / ((float)value->getElapsedTime()));
180 ALOGD("CACHE HIT #%d with start = %d, count = %d, contextCount = %d"
181 "- Compute time %0.6f ms - "
182 "Cache get time %0.6f ms - Gain in percent: %2.2f - Text = '%s'",
183 mCacheHitCount, start, count, contextCount,
184 value->getElapsedTime() * 0.000001f,
185 elapsedTimeThruCacheGet * 0.000001f,
187 String8(key.getText() + start, count).string());
189 if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) {
197 void TextLayoutCache::dumpCacheStats() {
198 float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize));
199 float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000;
201 size_t cacheSize = mCache.size();
203 ALOGD("------------------------------------------------");
204 ALOGD("Cache stats");
205 ALOGD("------------------------------------------------");
206 ALOGD("pid : %d", getpid());
207 ALOGD("running : %.0f seconds", timeRunningInSec);
208 ALOGD("entries : %d", cacheSize);
209 ALOGD("max size : %d bytes", mMaxSize);
210 ALOGD("used : %d bytes according to mSize", mSize);
211 ALOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent);
212 ALOGD("hits : %d", mCacheHitCount);
213 ALOGD("saved : %0.6f ms", mNanosecondsSaved * 0.000001f);
214 ALOGD("------------------------------------------------");
220 TextLayoutCacheKey::TextLayoutCacheKey(): start(0), count(0), contextCount(0),
221 dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0),
222 hinting(SkPaint::kNo_Hinting), variant(SkPaint::kDefault_Variant), language() {
225 TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text,
226 size_t start, size_t count, size_t contextCount, int dirFlags) :
227 start(start), count(count), contextCount(contextCount),
229 textCopy.setTo(text, contextCount);
230 typeface = paint->getTypeface();
231 textSize = paint->getTextSize();
232 textSkewX = paint->getTextSkewX();
233 textScaleX = paint->getTextScaleX();
234 flags = paint->getFlags();
235 hinting = paint->getHinting();
236 variant = paint->getFontVariant();
237 language = paint->getLanguage();
240 TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) :
241 textCopy(other.textCopy),
244 contextCount(other.contextCount),
245 dirFlags(other.dirFlags),
246 typeface(other.typeface),
247 textSize(other.textSize),
248 textSkewX(other.textSkewX),
249 textScaleX(other.textScaleX),
251 hinting(other.hinting),
252 variant(other.variant),
253 language(other.language) {
256 int TextLayoutCacheKey::compare(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) {
257 int deltaInt = lhs.start - rhs.start;
258 if (deltaInt != 0) return (deltaInt);
260 deltaInt = lhs.count - rhs.count;
261 if (deltaInt != 0) return (deltaInt);
263 deltaInt = lhs.contextCount - rhs.contextCount;
264 if (deltaInt != 0) return (deltaInt);
266 if (lhs.typeface < rhs.typeface) return -1;
267 if (lhs.typeface > rhs.typeface) return +1;
269 if (lhs.textSize < rhs.textSize) return -1;
270 if (lhs.textSize > rhs.textSize) return +1;
272 if (lhs.textSkewX < rhs.textSkewX) return -1;
273 if (lhs.textSkewX > rhs.textSkewX) return +1;
275 if (lhs.textScaleX < rhs.textScaleX) return -1;
276 if (lhs.textScaleX > rhs.textScaleX) return +1;
278 deltaInt = lhs.flags - rhs.flags;
279 if (deltaInt != 0) return (deltaInt);
281 deltaInt = lhs.hinting - rhs.hinting;
282 if (deltaInt != 0) return (deltaInt);
284 deltaInt = lhs.dirFlags - rhs.dirFlags;
285 if (deltaInt) return (deltaInt);
287 deltaInt = lhs.variant - rhs.variant;
288 if (deltaInt) return (deltaInt);
290 if (lhs.language < rhs.language) return -1;
291 if (lhs.language > rhs.language) return +1;
293 return memcmp(lhs.getText(), rhs.getText(), lhs.contextCount * sizeof(UChar));
296 size_t TextLayoutCacheKey::getSize() const {
297 return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount;
300 hash_t TextLayoutCacheKey::hash() const {
301 uint32_t hash = JenkinsHashMix(0, start);
302 hash = JenkinsHashMix(hash, count);
303 /* contextCount not needed because it's included in text, below */
304 hash = JenkinsHashMix(hash, hash_type(typeface));
305 hash = JenkinsHashMix(hash, hash_type(textSize));
306 hash = JenkinsHashMix(hash, hash_type(textSkewX));
307 hash = JenkinsHashMix(hash, hash_type(textScaleX));
308 hash = JenkinsHashMix(hash, flags);
309 hash = JenkinsHashMix(hash, hinting);
310 hash = JenkinsHashMix(hash, variant);
311 // Note: leaving out language is not problematic, as equality comparisons
312 // are still valid - the only bad thing that could happen is collisions.
313 hash = JenkinsHashMixShorts(hash, getText(), contextCount);
314 return JenkinsHashWhiten(hash);
318 * TextLayoutCacheValue
320 TextLayoutValue::TextLayoutValue(size_t contextCount) :
321 mTotalAdvance(0), mElapsedTime(0) {
322 // Give a hint for advances and glyphs vectors size
323 mAdvances.setCapacity(contextCount);
324 mGlyphs.setCapacity(contextCount);
325 mPos.setCapacity(contextCount * 2);
328 size_t TextLayoutValue::getSize() const {
329 return sizeof(TextLayoutValue) + sizeof(jfloat) * mAdvances.capacity() +
330 sizeof(jchar) * mGlyphs.capacity() + sizeof(jfloat) * mPos.capacity();
333 void TextLayoutValue::setElapsedTime(uint32_t time) {
337 uint32_t TextLayoutValue::getElapsedTime() {
341 TextLayoutShaper::TextLayoutShaper() {
344 mBuffer = hb_buffer_create();
347 void TextLayoutShaper::init() {
348 mDefaultTypeface = SkFontHost::CreateTypeface(NULL, NULL, SkTypeface::kNormal);
351 void TextLayoutShaper::unrefTypefaces() {
352 SkSafeUnref(mDefaultTypeface);
355 TextLayoutShaper::~TextLayoutShaper() {
356 hb_buffer_destroy(mBuffer);
361 void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* paint, const UChar* chars,
362 size_t start, size_t count, size_t contextCount, int dirFlags) {
364 computeValues(paint, chars, start, count, contextCount, dirFlags,
365 &value->mAdvances, &value->mTotalAdvance, &value->mGlyphs, &value->mPos);
367 ALOGD("Advances - start = %d, count = %d, contextCount = %d, totalAdvance = %f", start, count,
368 contextCount, value->mTotalAdvance);
372 void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars,
373 size_t start, size_t count, size_t contextCount, int dirFlags,
374 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
375 Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) {
376 *outTotalAdvance = 0;
381 UBiDiLevel bidiReq = 0;
382 bool forceLTR = false;
383 bool forceRTL = false;
385 switch (dirFlags & kBidi_Mask) {
386 case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
387 case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
388 case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
389 case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
390 case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR
391 case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL
394 bool useSingleRun = false;
395 bool isRTL = forceRTL;
396 if (forceLTR || forceRTL) {
399 UBiDi* bidi = ubidi_open();
401 UErrorCode status = U_ZERO_ERROR;
403 ALOGD("******** ComputeValues -- start");
404 ALOGD(" -- string = '%s'", String8(chars + start, count).string());
405 ALOGD(" -- start = %d", start);
406 ALOGD(" -- count = %d", count);
407 ALOGD(" -- contextCount = %d", contextCount);
408 ALOGD(" -- bidiReq = %d", bidiReq);
410 ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
411 if (U_SUCCESS(status)) {
412 int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
413 ssize_t rc = ubidi_countRuns(bidi, &status);
415 ALOGD(" -- dirFlags = %d", dirFlags);
416 ALOGD(" -- paraDir = %d", paraDir);
417 ALOGD(" -- run-count = %d", int(rc));
419 if (U_SUCCESS(status) && rc == 1) {
420 // Normal case: one run, status is ok
421 isRTL = (paraDir == 1);
423 } else if (!U_SUCCESS(status) || rc < 1) {
424 ALOGW("Need to force to single run -- string = '%s',"
425 " status = %d, rc = %d",
426 String8(chars + start, count).string(), status, int(rc));
427 isRTL = (paraDir == 1);
430 int32_t end = start + count;
431 for (size_t i = 0; i < size_t(rc); ++i) {
432 int32_t startRun = -1;
433 int32_t lengthRun = -1;
434 UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
436 if (startRun == -1 || lengthRun == -1) {
437 // Something went wrong when getting the visual run, need to clear
438 // already computed data before doing a single run pass
439 ALOGW("Visual run is not valid");
441 outAdvances->clear();
443 *outTotalAdvance = 0;
444 isRTL = (paraDir == 1);
449 if (startRun >= end) {
452 int32_t endRun = startRun + lengthRun;
453 if (endRun <= int32_t(start)) {
456 if (startRun < int32_t(start)) {
457 startRun = int32_t(start);
463 lengthRun = endRun - startRun;
464 isRTL = (runDir == UBIDI_RTL);
466 ALOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d",
467 i, startRun, lengthRun, isRTL);
469 computeRunValues(paint, chars, startRun, lengthRun, contextCount, isRTL,
470 outAdvances, outTotalAdvance, outGlyphs, outPos);
475 ALOGW("Cannot set Para");
477 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
481 ALOGW("Cannot ubidi_open()");
483 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
487 // Default single run case
490 ALOGD("Using a SINGLE BiDi Run "
491 "-- run-start = %d, run-len = %d, isRTL = %d", start, count, isRTL);
493 computeRunValues(paint, chars, start, count, contextCount, isRTL,
494 outAdvances, outTotalAdvance, outGlyphs, outPos);
498 ALOGD(" -- Total returned glyphs-count = %d", outGlyphs->size());
499 ALOGD("******** ComputeValues -- end");
503 #define HB_IsHighSurrogate(ucs) \
504 (((ucs) & 0xfc00) == 0xd800)
506 #define HB_IsLowSurrogate(ucs) \
507 (((ucs) & 0xfc00) == 0xdc00)
509 #ifndef HB_SurrogateToUcs4
510 #define HB_SurrogateToUcs4_(high, low) \
511 (((hb_codepoint_t)(high))<<10) + (low) - 0x35fdc00;
514 #define HB_InvalidCodePoint ~0u
517 utf16_to_code_point(const uint16_t *chars, size_t len, ssize_t *iter) {
518 const uint16_t v = chars[(*iter)++];
519 if (HB_IsHighSurrogate(v)) {
521 if (size_t(*iter) >= len) {
522 // the surrogate is incomplete.
523 return HB_InvalidCodePoint;
525 const uint16_t v2 = chars[(*iter)++];
526 if (!HB_IsLowSurrogate(v2)) {
527 // invalidate surrogate pair.
529 return HB_InvalidCodePoint;
532 return HB_SurrogateToUcs4(v, v2);
535 if (HB_IsLowSurrogate(v)) {
536 // this isn't a valid code point
537 return HB_InvalidCodePoint;
544 utf16_to_code_point_prev(const uint16_t *chars, size_t len, ssize_t *iter) {
545 const uint16_t v = chars[(*iter)--];
546 if (HB_IsLowSurrogate(v)) {
549 // the surrogate is incomplete.
550 return HB_InvalidCodePoint;
552 const uint16_t v2 = chars[(*iter)--];
553 if (!HB_IsHighSurrogate(v2)) {
554 // invalidate surrogate pair.
556 return HB_InvalidCodePoint;
559 return HB_SurrogateToUcs4(v2, v);
562 if (HB_IsHighSurrogate(v)) {
563 // this isn't a valid code point
564 return HB_InvalidCodePoint;
576 hb_script_t code_point_to_script(hb_codepoint_t codepoint) {
577 static hb_unicode_funcs_t* u;
579 u = hb_icu_get_unicode_funcs();
581 return hb_unicode_script(u, codepoint);
585 hb_utf16_script_run_next(ScriptRun* run, const uint16_t *chars, size_t len, ssize_t *iter) {
586 if (size_t(*iter) == len)
590 const uint32_t init_cp = utf16_to_code_point(chars, len, iter);
591 const hb_script_t init_script = code_point_to_script(init_cp);
592 hb_script_t current_script = init_script;
593 run->script = init_script;
596 if (size_t(*iter) == len)
598 const ssize_t prev_iter = *iter;
599 const uint32_t cp = utf16_to_code_point(chars, len, iter);
600 const hb_script_t script = code_point_to_script(cp);
602 if (script != current_script) {
603 /* BEGIN android-changed
604 The condition was not correct by doing "a == b == constant"
605 END android-changed */
606 if (current_script == HB_SCRIPT_INHERITED && init_script == HB_SCRIPT_INHERITED) {
607 // If we started off as inherited, we take whatever we can find.
608 run->script = script;
609 current_script = script;
611 } else if (script == HB_SCRIPT_INHERITED) {
620 if (run->script == HB_SCRIPT_INHERITED)
621 run->script = HB_SCRIPT_COMMON;
623 run->length = *iter - run->pos;
628 hb_utf16_script_run_prev(ScriptRun* run, const uint16_t *chars, size_t len, ssize_t *iter) {
632 const size_t ending_index = *iter;
633 const uint32_t init_cp = utf16_to_code_point_prev(chars, len, iter);
634 const hb_script_t init_script = code_point_to_script(init_cp);
635 hb_script_t current_script = init_script;
636 run->script = init_script;
641 const ssize_t prev_iter = *iter;
642 const uint32_t cp = utf16_to_code_point_prev(chars, len, iter);
643 const hb_script_t script = code_point_to_script(cp);
645 if (script != current_script) {
646 if (current_script == HB_SCRIPT_INHERITED && init_script == HB_SCRIPT_INHERITED) {
647 // If we started off as inherited, we take whatever we can find.
648 run->script = script;
649 current_script = script;
651 } else if (script == HB_SCRIPT_INHERITED) {
652 /* BEGIN android-changed
653 We apply the same fix for Chrome to Android.
654 Chrome team will talk with upsteam about it.
655 Just assume that whatever follows this combining character is within
656 the same script. This is incorrect if you had language1 + combining
657 char + language 2, but that is rare and this code is suspicious
659 END android-changed */
668 if (run->script == HB_SCRIPT_INHERITED)
669 run->script = HB_SCRIPT_COMMON;
671 run->pos = *iter + 1;
672 run->length = ending_index - *iter;
677 static void logGlyphs(hb_buffer_t* buffer) {
678 unsigned int numGlyphs;
679 hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer, &numGlyphs);
680 hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(buffer, NULL);
681 ALOGD(" -- glyphs count=%d", numGlyphs);
682 for (size_t i = 0; i < numGlyphs; i++) {
683 ALOGD(" -- glyph[%d] = %d, cluster = %u, advance = %0.2f, offset.x = %0.2f, offset.y = %0.2f", i,
686 HBFixedToFloat(positions[i].x_advance),
687 HBFixedToFloat(positions[i].x_offset),
688 HBFixedToFloat(positions[i].y_offset));
692 void TextLayoutShaper::computeRunValues(const SkPaint* paint, const UChar* contextChars,
693 size_t start, size_t count, size_t contextCount, bool isRTL,
694 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
695 Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) {
697 // We cannot shape an empty run.
701 // To be filled in later
702 for (size_t i = 0; i < count; i++) {
706 // Set the string properties
707 const UChar* chars = contextChars + start;
709 // Define shaping paint properties
710 mShapingPaint.setTextSize(paint->getTextSize());
711 float skewX = paint->getTextSkewX();
712 mShapingPaint.setTextSkewX(skewX);
713 mShapingPaint.setTextScaleX(paint->getTextScaleX());
714 mShapingPaint.setFlags(paint->getFlags());
715 mShapingPaint.setHinting(paint->getHinting());
716 mShapingPaint.setFontVariant(paint->getFontVariant());
717 mShapingPaint.setLanguage(paint->getLanguage());
719 // Split the BiDi run into Script runs. Harfbuzz will populate the pos, length and script
720 // into the shaperItem
721 ssize_t indexFontRun = isRTL ? count - 1 : 0;
722 jfloat totalAdvance = *outTotalAdvance;
723 ScriptRun run; // relative to chars
725 hb_utf16_script_run_prev(&run, chars, count, &indexFontRun):
726 hb_utf16_script_run_next(&run, chars, count, &indexFontRun)) {
729 ALOGD("-------- Start of Script Run --------");
730 ALOGD("Shaping Script Run with");
731 ALOGD(" -- isRTL = %d", isRTL);
732 ALOGD(" -- HB script = %c%c%c%c", HB_UNTAG(run.script));
733 ALOGD(" -- run.pos = %d", int(run.pos));
734 ALOGD(" -- run.length = %d", int(run.length));
735 ALOGD(" -- run = '%s'", String8(chars + run.pos, run.length).string());
736 ALOGD(" -- string = '%s'", String8(chars, count).string());
739 hb_buffer_reset(mBuffer);
740 // Note: if we want to set unicode functions, etc., this is the place.
742 hb_buffer_set_direction(mBuffer, isRTL ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
743 hb_buffer_set_script(mBuffer, run.script);
744 // Should set language here (for bug 7004056)
745 hb_buffer_add_utf16(mBuffer, contextChars, contextCount, start + run.pos, run.length);
747 // Initialize Harfbuzz Shaper and get the base glyph count for offsetting the glyphIDs
748 // and shape the Font run
749 size_t glyphBaseCount = shapeFontRun(paint);
750 unsigned int numGlyphs;
751 hb_glyph_info_t* info = hb_buffer_get_glyph_infos(mBuffer, &numGlyphs);
752 hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(mBuffer, NULL);
755 ALOGD("Got from Harfbuzz");
756 ALOGD(" -- glyphBaseCount = %d", glyphBaseCount);
757 ALOGD(" -- num_glyph = %d", numGlyphs);
758 ALOGD(" -- isDevKernText = %d", paint->isDevKernText());
759 ALOGD(" -- initial totalAdvance = %f", totalAdvance);
764 for (size_t i = 0; i < numGlyphs; i++) {
765 size_t cluster = info[i].cluster - start;
766 float xAdvance = HBFixedToFloat(positions[i].x_advance);
767 outAdvances->replaceAt(outAdvances->itemAt(cluster) + xAdvance, cluster);
768 outGlyphs->add(info[i].codepoint + glyphBaseCount);
769 float xo = HBFixedToFloat(positions[i].x_offset);
770 float yo = -HBFixedToFloat(positions[i].y_offset);
771 outPos->add(totalAdvance + xo + yo * skewX);
773 totalAdvance += xAdvance;
777 *outTotalAdvance = totalAdvance;
780 ALOGD(" -- final totalAdvance = %f", totalAdvance);
781 ALOGD("-------- End of Script Run --------");
786 * Return the first typeface in the logical change, starting with this typeface,
787 * that contains the specified unichar, or NULL if none is found.
789 * Note that this function does _not_ increment the reference count on the typeface, as the
790 * assumption is that its lifetime is managed elsewhere - in particular, the fallback typefaces
791 * for the default font live in a global cache.
793 SkTypeface* TextLayoutShaper::typefaceForScript(const SkPaint* paint, SkTypeface* typeface,
794 hb_script_t script) {
795 SkTypeface::Style currentStyle = SkTypeface::kNormal;
797 currentStyle = typeface->style();
799 typeface = SkCreateTypefaceForScriptNG(script, currentStyle);
801 ALOGD("Using Harfbuzz Script %d, Style %d", script, currentStyle);
806 bool TextLayoutShaper::isComplexScript(hb_script_t script) {
808 case HB_SCRIPT_COMMON:
809 case HB_SCRIPT_GREEK:
810 case HB_SCRIPT_CYRILLIC:
811 case HB_SCRIPT_HANGUL:
812 case HB_SCRIPT_INHERITED:
814 case HB_SCRIPT_KATAKANA:
815 case HB_SCRIPT_HIRAGANA:
822 size_t TextLayoutShaper::shapeFontRun(const SkPaint* paint) {
823 // Update Harfbuzz Shaper
825 SkTypeface* typeface = paint->getTypeface();
827 // Get the glyphs base count for offsetting the glyphIDs returned by Harfbuzz
828 // This is needed as the Typeface used for shaping can be not the default one
829 // when we are shaping any script that needs to use a fallback Font.
830 // If we are a "common" script we dont need to shift
831 size_t baseGlyphCount = 0;
832 hb_codepoint_t firstUnichar = 0;
833 if (isComplexScript(hb_buffer_get_script(mBuffer))) {
834 unsigned int numGlyphs;
835 hb_glyph_info_t* info = hb_buffer_get_glyph_infos(mBuffer, &numGlyphs);
836 for (size_t i = 0; i < numGlyphs; i++) {
837 firstUnichar = info[i].codepoint;
838 if (firstUnichar != ' ') {
842 baseGlyphCount = paint->getBaseGlyphCount(firstUnichar);
845 if (baseGlyphCount != 0) {
846 typeface = typefaceForScript(paint, typeface, hb_buffer_get_script(mBuffer));
848 typeface = mDefaultTypeface;
851 ALOGD("Using Default Typeface");
856 typeface = mDefaultTypeface;
858 ALOGD("Using Default Typeface");
864 mShapingPaint.setTypeface(typeface);
865 hb_face_t* face = referenceCachedHBFace(typeface);
867 float sizeY = paint->getTextSize();
868 float sizeX = sizeY * paint->getTextScaleX();
869 hb_font_t* font = createFont(face, &mShapingPaint, sizeX, sizeY);
870 hb_face_destroy(face);
873 ALOGD("Run typeface = %p, uniqueID = %d, face = %p",
874 typeface, typeface->uniqueID(), face);
876 SkSafeUnref(typeface);
878 hb_shape(font, mBuffer, NULL, 0);
879 hb_font_destroy(font);
881 return baseGlyphCount;
884 hb_face_t* TextLayoutShaper::referenceCachedHBFace(SkTypeface* typeface) {
885 SkFontID fontId = typeface->uniqueID();
886 ssize_t index = mCachedHBFaces.indexOfKey(fontId);
888 return hb_face_reference(mCachedHBFaces.valueAt(index));
890 // TODO: destroy function
891 hb_face_t* face = hb_face_create_for_tables(harfbuzzSkiaReferenceTable, typeface, NULL);
893 ALOGD("Created HB_NewFace %p from paint typeface = %p", face, typeface);
895 mCachedHBFaces.add(fontId, face);
896 return hb_face_reference(face);
899 void TextLayoutShaper::purgeCaches() {
900 size_t cacheSize = mCachedHBFaces.size();
901 for (size_t i = 0; i < cacheSize; i++) {
902 hb_face_destroy(mCachedHBFaces.valueAt(i));
904 mCachedHBFaces.clear();
909 TextLayoutEngine::TextLayoutEngine() {
910 mShaper = new TextLayoutShaper();
911 #if USE_TEXT_LAYOUT_CACHE
912 mTextLayoutCache = new TextLayoutCache(mShaper);
914 mTextLayoutCache = NULL;
918 TextLayoutEngine::~TextLayoutEngine() {
919 delete mTextLayoutCache;
923 sp<TextLayoutValue> TextLayoutEngine::getValue(const SkPaint* paint, const jchar* text,
924 jint start, jint count, jint contextCount, jint dirFlags) {
925 sp<TextLayoutValue> value;
926 #if USE_TEXT_LAYOUT_CACHE
927 value = mTextLayoutCache->getValue(paint, text, start, count,
928 contextCount, dirFlags);
930 ALOGE("Cannot get TextLayoutCache value for text = '%s'",
931 String8(text + start, count).string());
934 value = new TextLayoutValue(count);
935 mShaper->computeValues(value.get(), paint,
936 reinterpret_cast<const UChar*>(text), start, count, contextCount, dirFlags);
941 void TextLayoutEngine::purgeCaches() {
942 #if USE_TEXT_LAYOUT_CACHE
943 mTextLayoutCache->purgeCaches();
945 ALOGD("Purged TextLayoutEngine caches");
951 } // namespace android