• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 
17 #define LOG_TAG "TextLayoutCache"
18 
19 #include "TextLayoutCache.h"
20 #include "TextLayout.h"
21 #include "SkFontHost.h"
22 #include "SkTypeface_android.h"
23 #include <unicode/unistr.h>
24 #include <unicode/normlzr.h>
25 #include <unicode/uchar.h>
26 
27 extern "C" {
28   #include "harfbuzz-unicode.h"
29 }
30 
31 namespace android {
32 
33 //--------------------------------------------------------------------------------------------------
34 
35 ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutEngine);
36 
37 //--------------------------------------------------------------------------------------------------
38 
TextLayoutCache(TextLayoutShaper * shaper)39 TextLayoutCache::TextLayoutCache(TextLayoutShaper* shaper) :
40         mShaper(shaper),
41         mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutValue> >::kUnlimitedCapacity),
42         mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)),
43         mCacheHitCount(0), mNanosecondsSaved(0) {
44     init();
45 }
46 
~TextLayoutCache()47 TextLayoutCache::~TextLayoutCache() {
48     mCache.clear();
49 }
50 
init()51 void TextLayoutCache::init() {
52     mCache.setOnEntryRemovedListener(this);
53 
54     mDebugLevel = readRtlDebugLevel();
55     mDebugEnabled = mDebugLevel & kRtlDebugCaches;
56     ALOGD("Using debug level = %d - Debug Enabled = %d", mDebugLevel, mDebugEnabled);
57 
58     mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
59 
60     if (mDebugEnabled) {
61         ALOGD("Initialization is done - Start time = %lld", mCacheStartTime);
62     }
63 
64     mInitialized = true;
65 }
66 
67 /**
68  *  Callbacks
69  */
operator ()(TextLayoutCacheKey & text,sp<TextLayoutValue> & desc)70 void TextLayoutCache::operator()(TextLayoutCacheKey& text, sp<TextLayoutValue>& desc) {
71     size_t totalSizeToDelete = text.getSize() + desc->getSize();
72     mSize -= totalSizeToDelete;
73     if (mDebugEnabled) {
74         ALOGD("Cache value %p deleted, size = %d", desc.get(), totalSizeToDelete);
75     }
76 }
77 
78 /*
79  * Cache clearing
80  */
purgeCaches()81 void TextLayoutCache::purgeCaches() {
82     AutoMutex _l(mLock);
83     mCache.clear();
84     mShaper->purgeCaches();
85 }
86 
87 /*
88  * Caching
89  */
getValue(const SkPaint * paint,const jchar * text,jint start,jint count,jint contextCount,jint dirFlags)90 sp<TextLayoutValue> TextLayoutCache::getValue(const SkPaint* paint,
91             const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) {
92     AutoMutex _l(mLock);
93     nsecs_t startTime = 0;
94     if (mDebugEnabled) {
95         startTime = systemTime(SYSTEM_TIME_MONOTONIC);
96     }
97 
98     // Create the key
99     TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags);
100 
101     // Get value from cache if possible
102     sp<TextLayoutValue> value = mCache.get(key);
103 
104     // Value not found for the key, we need to add a new value in the cache
105     if (value == NULL) {
106         if (mDebugEnabled) {
107             startTime = systemTime(SYSTEM_TIME_MONOTONIC);
108         }
109 
110         value = new TextLayoutValue(contextCount);
111 
112         // Compute advances and store them
113         mShaper->computeValues(value.get(), paint,
114                 reinterpret_cast<const UChar*>(key.getText()), start, count,
115                 size_t(contextCount), int(dirFlags));
116 
117         if (mDebugEnabled) {
118             value->setElapsedTime(systemTime(SYSTEM_TIME_MONOTONIC) - startTime);
119         }
120 
121         // Don't bother to add in the cache if the entry is too big
122         size_t size = key.getSize() + value->getSize();
123         if (size <= mMaxSize) {
124             // Cleanup to make some room if needed
125             if (mSize + size > mMaxSize) {
126                 if (mDebugEnabled) {
127                     ALOGD("Need to clean some entries for making some room for a new entry");
128                 }
129                 while (mSize + size > mMaxSize) {
130                     // This will call the callback
131                     bool removedOne = mCache.removeOldest();
132                     LOG_ALWAYS_FATAL_IF(!removedOne, "The cache is non-empty but we "
133                             "failed to remove the oldest entry.  "
134                             "mSize = %u, size = %u, mMaxSize = %u, mCache.size() = %u",
135                             mSize, size, mMaxSize, mCache.size());
136                 }
137             }
138 
139             // Update current cache size
140             mSize += size;
141 
142             bool putOne = mCache.put(key, value);
143             LOG_ALWAYS_FATAL_IF(!putOne, "Failed to put an entry into the cache.  "
144                     "This indicates that the cache already has an entry with the "
145                     "same key but it should not since we checked earlier!"
146                     " - start = %d, count = %d, contextCount = %d - Text = '%s'",
147                     start, count, contextCount, String8(key.getText() + start, count).string());
148 
149             if (mDebugEnabled) {
150                 nsecs_t totalTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
151                 ALOGD("CACHE MISS: Added entry %p "
152                         "with start = %d, count = %d, contextCount = %d, "
153                         "entry size %d bytes, remaining space %d bytes"
154                         " - Compute time %0.6f ms - Put time %0.6f ms - Text = '%s'",
155                         value.get(), start, count, contextCount, size, mMaxSize - mSize,
156                         value->getElapsedTime() * 0.000001f,
157                         (totalTime - value->getElapsedTime()) * 0.000001f,
158                         String8(key.getText() + start, count).string());
159             }
160         } else {
161             if (mDebugEnabled) {
162                 ALOGD("CACHE MISS: Calculated but not storing entry because it is too big "
163                         "with start = %d, count = %d, contextCount = %d, "
164                         "entry size %d bytes, remaining space %d bytes"
165                         " - Compute time %0.6f ms - Text = '%s'",
166                         start, count, contextCount, size, mMaxSize - mSize,
167                         value->getElapsedTime() * 0.000001f,
168                         String8(key.getText() + start, count).string());
169             }
170         }
171     } else {
172         // This is a cache hit, just log timestamp and user infos
173         if (mDebugEnabled) {
174             nsecs_t elapsedTimeThruCacheGet = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
175             mNanosecondsSaved += (value->getElapsedTime() - elapsedTimeThruCacheGet);
176             ++mCacheHitCount;
177 
178             if (value->getElapsedTime() > 0) {
179                 float deltaPercent = 100 * ((value->getElapsedTime() - elapsedTimeThruCacheGet)
180                         / ((float)value->getElapsedTime()));
181                 ALOGD("CACHE HIT #%d with start = %d, count = %d, contextCount = %d"
182                         "- Compute time %0.6f ms - "
183                         "Cache get time %0.6f ms - Gain in percent: %2.2f - Text = '%s'",
184                         mCacheHitCount, start, count, contextCount,
185                         value->getElapsedTime() * 0.000001f,
186                         elapsedTimeThruCacheGet * 0.000001f,
187                         deltaPercent,
188                         String8(key.getText() + start, count).string());
189             }
190             if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) {
191                 dumpCacheStats();
192             }
193         }
194     }
195     return value;
196 }
197 
dumpCacheStats()198 void TextLayoutCache::dumpCacheStats() {
199     float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize));
200     float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000;
201 
202     size_t bytes = 0;
203     size_t cacheSize = mCache.size();
204     for (size_t i = 0; i < cacheSize; i++) {
205         bytes += mCache.getKeyAt(i).getSize() + mCache.getValueAt(i)->getSize();
206     }
207 
208     ALOGD("------------------------------------------------");
209     ALOGD("Cache stats");
210     ALOGD("------------------------------------------------");
211     ALOGD("pid       : %d", getpid());
212     ALOGD("running   : %.0f seconds", timeRunningInSec);
213     ALOGD("entries   : %d", cacheSize);
214     ALOGD("max size  : %d bytes", mMaxSize);
215     ALOGD("used      : %d bytes according to mSize, %d bytes actual", mSize, bytes);
216     ALOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent);
217     ALOGD("hits      : %d", mCacheHitCount);
218     ALOGD("saved     : %0.6f ms", mNanosecondsSaved * 0.000001f);
219     ALOGD("------------------------------------------------");
220 }
221 
222 /**
223  * TextLayoutCacheKey
224  */
TextLayoutCacheKey()225 TextLayoutCacheKey::TextLayoutCacheKey(): start(0), count(0), contextCount(0),
226         dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0),
227         hinting(SkPaint::kNo_Hinting), variant(SkPaint::kDefault_Variant), language()  {
228 }
229 
TextLayoutCacheKey(const SkPaint * paint,const UChar * text,size_t start,size_t count,size_t contextCount,int dirFlags)230 TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text,
231         size_t start, size_t count, size_t contextCount, int dirFlags) :
232             start(start), count(count), contextCount(contextCount),
233             dirFlags(dirFlags) {
234     textCopy.setTo(text, contextCount);
235     typeface = paint->getTypeface();
236     textSize = paint->getTextSize();
237     textSkewX = paint->getTextSkewX();
238     textScaleX = paint->getTextScaleX();
239     flags = paint->getFlags();
240     hinting = paint->getHinting();
241     variant = paint->getFontVariant();
242     language = paint->getLanguage();
243 }
244 
TextLayoutCacheKey(const TextLayoutCacheKey & other)245 TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) :
246         textCopy(other.textCopy),
247         start(other.start),
248         count(other.count),
249         contextCount(other.contextCount),
250         dirFlags(other.dirFlags),
251         typeface(other.typeface),
252         textSize(other.textSize),
253         textSkewX(other.textSkewX),
254         textScaleX(other.textScaleX),
255         flags(other.flags),
256         hinting(other.hinting),
257         variant(other.variant),
258         language(other.language) {
259 }
260 
compare(const TextLayoutCacheKey & lhs,const TextLayoutCacheKey & rhs)261 int TextLayoutCacheKey::compare(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) {
262     int deltaInt = lhs.start - rhs.start;
263     if (deltaInt != 0) return (deltaInt);
264 
265     deltaInt = lhs.count - rhs.count;
266     if (deltaInt != 0) return (deltaInt);
267 
268     deltaInt = lhs.contextCount - rhs.contextCount;
269     if (deltaInt != 0) return (deltaInt);
270 
271     if (lhs.typeface < rhs.typeface) return -1;
272     if (lhs.typeface > rhs.typeface) return +1;
273 
274     if (lhs.textSize < rhs.textSize) return -1;
275     if (lhs.textSize > rhs.textSize) return +1;
276 
277     if (lhs.textSkewX < rhs.textSkewX) return -1;
278     if (lhs.textSkewX > rhs.textSkewX) return +1;
279 
280     if (lhs.textScaleX < rhs.textScaleX) return -1;
281     if (lhs.textScaleX > rhs.textScaleX) return +1;
282 
283     deltaInt = lhs.flags - rhs.flags;
284     if (deltaInt != 0) return (deltaInt);
285 
286     deltaInt = lhs.hinting - rhs.hinting;
287     if (deltaInt != 0) return (deltaInt);
288 
289     deltaInt = lhs.dirFlags - rhs.dirFlags;
290     if (deltaInt) return (deltaInt);
291 
292     deltaInt = lhs.variant - rhs.variant;
293     if (deltaInt) return (deltaInt);
294 
295     if (lhs.language < rhs.language) return -1;
296     if (lhs.language > rhs.language) return +1;
297 
298     return memcmp(lhs.getText(), rhs.getText(), lhs.contextCount * sizeof(UChar));
299 }
300 
getSize() const301 size_t TextLayoutCacheKey::getSize() const {
302     return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount;
303 }
304 
305 /**
306  * TextLayoutCacheValue
307  */
TextLayoutValue(size_t contextCount)308 TextLayoutValue::TextLayoutValue(size_t contextCount) :
309         mTotalAdvance(0), mElapsedTime(0) {
310     // Give a hint for advances and glyphs vectors size
311     mAdvances.setCapacity(contextCount);
312     mGlyphs.setCapacity(contextCount);
313     mPos.setCapacity(contextCount * 2);
314 }
315 
getSize() const316 size_t TextLayoutValue::getSize() const {
317     return sizeof(TextLayoutValue) + sizeof(jfloat) * mAdvances.capacity() +
318             sizeof(jchar) * mGlyphs.capacity() + sizeof(jfloat) * mPos.capacity();
319 }
320 
setElapsedTime(uint32_t time)321 void TextLayoutValue::setElapsedTime(uint32_t time) {
322     mElapsedTime = time;
323 }
324 
getElapsedTime()325 uint32_t TextLayoutValue::getElapsedTime() {
326     return mElapsedTime;
327 }
328 
TextLayoutShaper()329 TextLayoutShaper::TextLayoutShaper() : mShaperItemGlyphArraySize(0) {
330     init();
331 
332     mFontRec.klass = &harfbuzzSkiaClass;
333     mFontRec.userData = 0;
334 
335     // Note that the scaling values (x_ and y_ppem, x_ and y_scale) will be set
336     // below, when the paint transform and em unit of the actual shaping font
337     // are known.
338 
339     memset(&mShaperItem, 0, sizeof(mShaperItem));
340 
341     mShaperItem.font = &mFontRec;
342     mShaperItem.font->userData = &mShapingPaint;
343 }
344 
init()345 void TextLayoutShaper::init() {
346     mDefaultTypeface = SkFontHost::CreateTypeface(NULL, NULL, NULL, 0, SkTypeface::kNormal);
347 }
348 
unrefTypefaces()349 void TextLayoutShaper::unrefTypefaces() {
350     SkSafeUnref(mDefaultTypeface);
351 }
352 
~TextLayoutShaper()353 TextLayoutShaper::~TextLayoutShaper() {
354     unrefTypefaces();
355     deleteShaperItemGlyphArrays();
356 }
357 
computeValues(TextLayoutValue * value,const SkPaint * paint,const UChar * chars,size_t start,size_t count,size_t contextCount,int dirFlags)358 void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* paint, const UChar* chars,
359         size_t start, size_t count, size_t contextCount, int dirFlags) {
360 
361     computeValues(paint, chars, start, count, contextCount, dirFlags,
362             &value->mAdvances, &value->mTotalAdvance, &value->mGlyphs, &value->mPos);
363 #if DEBUG_ADVANCES
364     ALOGD("Advances - start = %d, count = %d, contextCount = %d, totalAdvance = %f", start, count,
365             contextCount, value->mTotalAdvance);
366 #endif
367 }
368 
computeValues(const SkPaint * paint,const UChar * chars,size_t start,size_t count,size_t contextCount,int dirFlags,Vector<jfloat> * const outAdvances,jfloat * outTotalAdvance,Vector<jchar> * const outGlyphs,Vector<jfloat> * const outPos)369 void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars,
370         size_t start, size_t count, size_t contextCount, int dirFlags,
371         Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
372         Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) {
373         *outTotalAdvance = 0;
374         if (!count) {
375             return;
376         }
377 
378         UBiDiLevel bidiReq = 0;
379         bool forceLTR = false;
380         bool forceRTL = false;
381 
382         switch (dirFlags) {
383             case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
384             case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
385             case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
386             case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
387             case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR
388             case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL
389         }
390 
391         bool useSingleRun = false;
392         bool isRTL = forceRTL;
393         if (forceLTR || forceRTL) {
394             useSingleRun = true;
395         } else {
396             UBiDi* bidi = ubidi_open();
397             if (bidi) {
398                 UErrorCode status = U_ZERO_ERROR;
399 #if DEBUG_GLYPHS
400                 ALOGD("******** ComputeValues -- start");
401                 ALOGD("      -- string = '%s'", String8(chars + start, count).string());
402                 ALOGD("      -- start = %d", start);
403                 ALOGD("      -- count = %d", count);
404                 ALOGD("      -- contextCount = %d", contextCount);
405                 ALOGD("      -- bidiReq = %d", bidiReq);
406 #endif
407                 ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
408                 if (U_SUCCESS(status)) {
409                     int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
410                     ssize_t rc = ubidi_countRuns(bidi, &status);
411 #if DEBUG_GLYPHS
412                     ALOGD("      -- dirFlags = %d", dirFlags);
413                     ALOGD("      -- paraDir = %d", paraDir);
414                     ALOGD("      -- run-count = %d", int(rc));
415 #endif
416                     if (U_SUCCESS(status) && rc == 1) {
417                         // Normal case: one run, status is ok
418                         isRTL = (paraDir == 1);
419                         useSingleRun = true;
420                     } else if (!U_SUCCESS(status) || rc < 1) {
421                         ALOGW("Need to force to single run -- string = '%s',"
422                                 " status = %d, rc = %d",
423                                 String8(chars + start, count).string(), status, int(rc));
424                         isRTL = (paraDir == 1);
425                         useSingleRun = true;
426                     } else {
427                         int32_t end = start + count;
428                         for (size_t i = 0; i < size_t(rc); ++i) {
429                             int32_t startRun = -1;
430                             int32_t lengthRun = -1;
431                             UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
432 
433                             if (startRun == -1 || lengthRun == -1) {
434                                 // Something went wrong when getting the visual run, need to clear
435                                 // already computed data before doing a single run pass
436                                 ALOGW("Visual run is not valid");
437                                 outGlyphs->clear();
438                                 outAdvances->clear();
439                                 outPos->clear();
440                                 *outTotalAdvance = 0;
441                                 isRTL = (paraDir == 1);
442                                 useSingleRun = true;
443                                 break;
444                             }
445 
446                             if (startRun >= end) {
447                                 continue;
448                             }
449                             int32_t endRun = startRun + lengthRun;
450                             if (endRun <= int32_t(start)) {
451                                 continue;
452                             }
453                             if (startRun < int32_t(start)) {
454                                 startRun = int32_t(start);
455                             }
456                             if (endRun > end) {
457                                 endRun = end;
458                             }
459 
460                             lengthRun = endRun - startRun;
461                             isRTL = (runDir == UBIDI_RTL);
462 #if DEBUG_GLYPHS
463                             ALOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d",
464                                     i, startRun, lengthRun, isRTL);
465 #endif
466                             computeRunValues(paint, chars + startRun, lengthRun, isRTL,
467                                     outAdvances, outTotalAdvance, outGlyphs, outPos);
468 
469                         }
470                     }
471                 } else {
472                     ALOGW("Cannot set Para");
473                     useSingleRun = true;
474                     isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
475                 }
476                 ubidi_close(bidi);
477             } else {
478                 ALOGW("Cannot ubidi_open()");
479                 useSingleRun = true;
480                 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
481             }
482         }
483 
484         // Default single run case
485         if (useSingleRun){
486 #if DEBUG_GLYPHS
487             ALOGD("Using a SINGLE BiDi Run "
488                     "-- run-start = %d, run-len = %d, isRTL = %d", start, count, isRTL);
489 #endif
490             computeRunValues(paint, chars + start, count, isRTL,
491                     outAdvances, outTotalAdvance, outGlyphs, outPos);
492         }
493 
494 #if DEBUG_GLYPHS
495         ALOGD("      -- Total returned glyphs-count = %d", outGlyphs->size());
496         ALOGD("******** ComputeValues -- end");
497 #endif
498 }
499 
logGlyphs(HB_ShaperItem shaperItem)500 static void logGlyphs(HB_ShaperItem shaperItem) {
501     ALOGD("         -- glyphs count=%d", shaperItem.num_glyphs);
502     for (size_t i = 0; i < shaperItem.num_glyphs; i++) {
503         ALOGD("         -- glyph[%d] = %d, offset.x = %0.2f, offset.y = %0.2f", i,
504                 shaperItem.glyphs[i],
505                 HBFixedToFloat(shaperItem.offsets[i].x),
506                 HBFixedToFloat(shaperItem.offsets[i].y));
507     }
508 }
509 
computeRunValues(const SkPaint * paint,const UChar * chars,size_t count,bool isRTL,Vector<jfloat> * const outAdvances,jfloat * outTotalAdvance,Vector<jchar> * const outGlyphs,Vector<jfloat> * const outPos)510 void TextLayoutShaper::computeRunValues(const SkPaint* paint, const UChar* chars,
511         size_t count, bool isRTL,
512         Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
513         Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) {
514     if (!count) {
515         // We cannot shape an empty run.
516         return;
517     }
518 
519     // To be filled in later
520     for (size_t i = 0; i < count; i++) {
521         outAdvances->add(0);
522     }
523     UErrorCode error = U_ZERO_ERROR;
524     bool useNormalizedString = false;
525     for (ssize_t i = count - 1; i >= 0; --i) {
526         UChar ch1 = chars[i];
527         if (::ublock_getCode(ch1) == UBLOCK_COMBINING_DIACRITICAL_MARKS) {
528             // So we have found a diacritic, let's get now the main code point which is paired
529             // with it. As we can have several diacritics in a row, we need to iterate back again
530 #if DEBUG_GLYPHS
531             ALOGD("The BiDi run '%s' is containing a Diacritic at position %d",
532                     String8(chars, count).string(), int(i));
533 #endif
534             ssize_t j = i - 1;
535             for (; j >= 0;  --j) {
536                 UChar ch2 = chars[j];
537                 if (::ublock_getCode(ch2) != UBLOCK_COMBINING_DIACRITICAL_MARKS) {
538                     break;
539                 }
540             }
541 
542             // We could not found the main code point, so we will just use the initial chars
543             if (j < 0) {
544                 break;
545             }
546 
547 #if DEBUG_GLYPHS
548             ALOGD("Found main code point at index %d", int(j));
549 #endif
550             // We found the main code point, so we can normalize the "chunk" and fill
551             // the remaining with ZWSP so that the Paint.getTextWidth() APIs will still be able
552             // to get one advance per char
553             mBuffer.remove();
554             Normalizer::normalize(UnicodeString(chars + j, i - j + 1),
555                     UNORM_NFC, 0 /* no options */, mBuffer, error);
556             if (U_SUCCESS(error)) {
557                 if (!useNormalizedString) {
558                     useNormalizedString = true;
559                     mNormalizedString.setTo(false /* not terminated*/, chars, count);
560                 }
561                 // Set the normalized chars
562                 for (ssize_t k = j; k < j + mBuffer.length(); ++k) {
563                     mNormalizedString.setCharAt(k, mBuffer.charAt(k - j));
564                 }
565                 // Fill the remain part with ZWSP (ZWNJ and ZWJ would lead to weird results
566                 // because some fonts are missing those glyphs)
567                 for (ssize_t k = j + mBuffer.length(); k <= i; ++k) {
568                     mNormalizedString.setCharAt(k, UNICODE_ZWSP);
569                 }
570             }
571             i = j - 1;
572         }
573     }
574 
575     // Reverse "BiDi mirrored chars" in RTL mode only
576     // See: http://www.unicode.org/Public/6.0.0/ucd/extracted/DerivedBinaryProperties.txt
577     // This is a workaround because Harfbuzz is not able to do mirroring in all cases and
578     // script-run splitting with Harfbuzz is splitting on parenthesis
579     if (isRTL) {
580         for (ssize_t i = 0; i < ssize_t(count); i++) {
581             UChar32 ch = chars[i];
582             if (!u_isMirrored(ch)) continue;
583             if (!useNormalizedString) {
584                 useNormalizedString = true;
585                 mNormalizedString.setTo(false /* not terminated*/, chars, count);
586             }
587             UChar result =  (UChar) u_charMirror(ch);
588             mNormalizedString.setCharAt(i, result);
589 #if DEBUG_GLYPHS
590             ALOGD("Rewriting codepoint '%d' to '%d' at position %d",
591                     ch, mNormalizedString[i], int(i));
592 #endif
593         }
594     }
595 
596 #if DEBUG_GLYPHS
597     if (useNormalizedString) {
598         ALOGD("Will use normalized string '%s', length = %d",
599                     String8(mNormalizedString.getTerminatedBuffer(),
600                             mNormalizedString.length()).string(),
601                     mNormalizedString.length());
602     } else {
603         ALOGD("Normalization is not needed or cannot be done, using initial string");
604     }
605 #endif
606 
607     assert(mNormalizedString.length() == count);
608 
609     // Set the string properties
610     mShaperItem.string = useNormalizedString ? mNormalizedString.getTerminatedBuffer() : chars;
611     mShaperItem.stringLength = count;
612 
613     // Define shaping paint properties
614     mShapingPaint.setTextSize(paint->getTextSize());
615     float skewX = paint->getTextSkewX();
616     mShapingPaint.setTextSkewX(skewX);
617     mShapingPaint.setTextScaleX(paint->getTextScaleX());
618     mShapingPaint.setFlags(paint->getFlags());
619     mShapingPaint.setHinting(paint->getHinting());
620     mShapingPaint.setFontVariant(paint->getFontVariant());
621     mShapingPaint.setLanguage(paint->getLanguage());
622 
623     // Split the BiDi run into Script runs. Harfbuzz will populate the pos, length and script
624     // into the shaperItem
625     ssize_t indexFontRun = isRTL ? mShaperItem.stringLength - 1 : 0;
626     unsigned numCodePoints = 0;
627     jfloat totalAdvance = *outTotalAdvance;
628     while ((isRTL) ?
629             hb_utf16_script_run_prev(&numCodePoints, &mShaperItem.item, mShaperItem.string,
630                     mShaperItem.stringLength, &indexFontRun):
631             hb_utf16_script_run_next(&numCodePoints, &mShaperItem.item, mShaperItem.string,
632                     mShaperItem.stringLength, &indexFontRun)) {
633 
634         ssize_t startScriptRun = mShaperItem.item.pos;
635         size_t countScriptRun = mShaperItem.item.length;
636         ssize_t endScriptRun = startScriptRun + countScriptRun;
637 
638 #if DEBUG_GLYPHS
639         ALOGD("-------- Start of Script Run --------");
640         ALOGD("Shaping Script Run with");
641         ALOGD("         -- isRTL = %d", isRTL);
642         ALOGD("         -- HB script = %d", mShaperItem.item.script);
643         ALOGD("         -- startFontRun = %d", int(startScriptRun));
644         ALOGD("         -- endFontRun = %d", int(endScriptRun));
645         ALOGD("         -- countFontRun = %d", countScriptRun);
646         ALOGD("         -- run = '%s'", String8(chars + startScriptRun, countScriptRun).string());
647         ALOGD("         -- string = '%s'", String8(chars, count).string());
648 #endif
649 
650         // Initialize Harfbuzz Shaper and get the base glyph count for offsetting the glyphIDs
651         // and shape the Font run
652         size_t glyphBaseCount = shapeFontRun(paint, isRTL);
653 
654 #if DEBUG_GLYPHS
655         ALOGD("Got from Harfbuzz");
656         ALOGD("         -- glyphBaseCount = %d", glyphBaseCount);
657         ALOGD("         -- num_glypth = %d", mShaperItem.num_glyphs);
658         ALOGD("         -- kerning_applied = %d", mShaperItem.kerning_applied);
659         ALOGD("         -- isDevKernText = %d", paint->isDevKernText());
660 
661         logGlyphs(mShaperItem);
662 #endif
663 
664         if (mShaperItem.advances == NULL || mShaperItem.num_glyphs == 0) {
665 #if DEBUG_GLYPHS
666             ALOGD("Advances array is empty or num_glypth = 0");
667 #endif
668             continue;
669         }
670 
671 #if DEBUG_GLYPHS
672         ALOGD("Returned logclusters");
673         for (size_t i = 0; i < mShaperItem.num_glyphs; i++) {
674             ALOGD("         -- lc[%d] = %d, hb-adv[%d] = %0.2f", i, mShaperItem.log_clusters[i],
675                     i, HBFixedToFloat(mShaperItem.advances[i]));
676         }
677 #endif
678         jfloat totalFontRunAdvance = 0;
679         size_t clusterStart = 0;
680         for (size_t i = 0; i < countScriptRun; i++) {
681             size_t cluster = mShaperItem.log_clusters[i];
682             size_t clusterNext = i == countScriptRun - 1 ? mShaperItem.num_glyphs :
683                 mShaperItem.log_clusters[i + 1];
684             if (cluster != clusterNext) {
685                 jfloat advance = 0;
686                 // The advance for the cluster is the sum of the advances of all glyphs within
687                 // the cluster.
688                 for (size_t j = cluster; j < clusterNext; j++) {
689                     advance += HBFixedToFloat(mShaperItem.advances[j]);
690                 }
691                 totalFontRunAdvance += advance;
692                 outAdvances->replaceAt(advance, startScriptRun + clusterStart);
693                 clusterStart = i + 1;
694             }
695         }
696 
697 #if DEBUG_ADVANCES
698         ALOGD("Returned advances");
699         for (size_t i = 0; i < countScriptRun; i++) {
700             ALOGD("         -- hb-adv[%d] = %0.2f, log_clusters = %d, total = %0.2f", i,
701                     (*outAdvances)[i], mShaperItem.log_clusters[i], totalFontRunAdvance);
702         }
703 #endif
704 
705         // Get Glyphs and reverse them in place if RTL
706         if (outGlyphs) {
707             size_t countGlyphs = mShaperItem.num_glyphs;
708 #if DEBUG_GLYPHS
709             ALOGD("Returned script run glyphs -- count = %d", countGlyphs);
710 #endif
711             for (size_t i = 0; i < countGlyphs; i++) {
712                 jchar glyph = glyphBaseCount +
713                         (jchar) mShaperItem.glyphs[(!isRTL) ? i : countGlyphs - 1 - i];
714 #if DEBUG_GLYPHS
715                 ALOGD("         -- glyph[%d] = %d", i, glyph);
716 #endif
717                 outGlyphs->add(glyph);
718             }
719         }
720 
721         // Get glyph positions (and reverse them in place if RTL)
722         if (outPos) {
723             size_t countGlyphs = mShaperItem.num_glyphs;
724             jfloat x = totalAdvance;
725             for (size_t i = 0; i < countGlyphs; i++) {
726                 size_t index = (!isRTL) ? i : countGlyphs - 1 - i;
727                 float xo = HBFixedToFloat(mShaperItem.offsets[index].x);
728                 float yo = HBFixedToFloat(mShaperItem.offsets[index].y);
729                 // Apply skewX component of transform to position offsets. Note
730                 // that scale has already been applied through x_ and y_scale
731                 // set in the mFontRec.
732                 outPos->add(x + xo + yo * skewX);
733                 outPos->add(yo);
734 #if DEBUG_GLYPHS
735                 ALOGD("         -- hb adv[%d] = %f, log_cluster[%d] = %d",
736                         index, HBFixedToFloat(mShaperItem.advances[index]),
737                         index, mShaperItem.log_clusters[index]);
738 #endif
739                 x += HBFixedToFloat(mShaperItem.advances[index]);
740             }
741         }
742 
743         totalAdvance += totalFontRunAdvance;
744     }
745 
746     *outTotalAdvance = totalAdvance;
747 
748 #if DEBUG_GLYPHS
749     ALOGD("-------- End of Script Run --------");
750 #endif
751 }
752 
753 /**
754  * Return the first typeface in the logical change, starting with this typeface,
755  * that contains the specified unichar, or NULL if none is found.
756  *
757  * Note that this function does _not_ increment the reference count on the typeface, as the
758  * assumption is that its lifetime is managed elsewhere - in particular, the fallback typefaces
759  * for the default font live in a global cache.
760  */
typefaceForScript(const SkPaint * paint,SkTypeface * typeface,HB_Script script)761 SkTypeface* TextLayoutShaper::typefaceForScript(const SkPaint* paint, SkTypeface* typeface,
762         HB_Script script) {
763     SkTypeface::Style currentStyle = SkTypeface::kNormal;
764     if (typeface) {
765         currentStyle = typeface->style();
766     }
767     typeface = SkCreateTypefaceForScript(script, currentStyle);
768 #if DEBUG_GLYPHS
769     ALOGD("Using Harfbuzz Script %d, Style %d", script, currentStyle);
770 #endif
771     return typeface;
772 }
773 
isComplexScript(HB_Script script)774 bool TextLayoutShaper::isComplexScript(HB_Script script) {
775     switch (script) {
776     case HB_Script_Common:
777     case HB_Script_Greek:
778     case HB_Script_Cyrillic:
779     case HB_Script_Hangul:
780     case HB_Script_Inherited:
781         return false;
782     default:
783         return true;
784     }
785 }
786 
shapeFontRun(const SkPaint * paint,bool isRTL)787 size_t TextLayoutShaper::shapeFontRun(const SkPaint* paint, bool isRTL) {
788     // Reset kerning
789     mShaperItem.kerning_applied = false;
790 
791     // Update Harfbuzz Shaper
792     mShaperItem.item.bidiLevel = isRTL;
793 
794     SkTypeface* typeface = paint->getTypeface();
795 
796     // Get the glyphs base count for offsetting the glyphIDs returned by Harfbuzz
797     // This is needed as the Typeface used for shaping can be not the default one
798     // when we are shaping any script that needs to use a fallback Font.
799     // If we are a "common" script we dont need to shift
800     size_t baseGlyphCount = 0;
801     SkUnichar firstUnichar = 0;
802     if (isComplexScript(mShaperItem.item.script)) {
803         const uint16_t* text16 = (const uint16_t*) (mShaperItem.string + mShaperItem.item.pos);
804         const uint16_t* text16End = text16 + mShaperItem.item.length;
805         firstUnichar = SkUTF16_NextUnichar(&text16);
806         while (firstUnichar == ' ' && text16 < text16End) {
807             firstUnichar = SkUTF16_NextUnichar(&text16);
808         }
809         baseGlyphCount = paint->getBaseGlyphCount(firstUnichar);
810     }
811 
812     if (baseGlyphCount != 0) {
813         typeface = typefaceForScript(paint, typeface, mShaperItem.item.script);
814         if (!typeface) {
815             typeface = mDefaultTypeface;
816             SkSafeRef(typeface);
817 #if DEBUG_GLYPHS
818             ALOGD("Using Default Typeface");
819 #endif
820         }
821     } else {
822         if (!typeface) {
823             typeface = mDefaultTypeface;
824 #if DEBUG_GLYPHS
825             ALOGD("Using Default Typeface");
826 #endif
827         }
828         SkSafeRef(typeface);
829     }
830 
831     mShapingPaint.setTypeface(typeface);
832     mShaperItem.face = getCachedHBFace(typeface);
833 
834     int textSize = paint->getTextSize();
835     float scaleX = paint->getTextScaleX();
836     mFontRec.x_ppem = floor(scaleX * textSize + 0.5);
837     mFontRec.y_ppem = textSize;
838     uint32_t unitsPerEm = SkFontHost::GetUnitsPerEm(typeface->uniqueID());
839     // x_ and y_scale are the conversion factors from font design space
840     // (unitsPerEm) to 1/64th of device pixels in 16.16 format.
841     const int kDevicePixelFraction = 64;
842     const int kMultiplyFor16Dot16 = 1 << 16;
843     float emScale = kDevicePixelFraction * kMultiplyFor16Dot16 / (float)unitsPerEm;
844     mFontRec.x_scale = emScale * scaleX * textSize;
845     mFontRec.y_scale = emScale * textSize;
846 
847 #if DEBUG_GLYPHS
848     ALOGD("Run typeface = %p, uniqueID = %d, hb_face = %p",
849             typeface, typeface->uniqueID(), mShaperItem.face);
850 #endif
851     SkSafeUnref(typeface);
852 
853     // Shape
854     assert(mShaperItem.item.length > 0); // Harfbuzz will overwrite other memory if length is 0.
855     size_t size = mShaperItem.item.length * 3 / 2;
856     while (!doShaping(size)) {
857         // We overflowed our glyph arrays. Resize and retry.
858         // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size.
859         size = mShaperItem.num_glyphs * 2;
860     }
861     return baseGlyphCount;
862 }
863 
doShaping(size_t size)864 bool TextLayoutShaper::doShaping(size_t size) {
865     if (size > mShaperItemGlyphArraySize) {
866         deleteShaperItemGlyphArrays();
867         createShaperItemGlyphArrays(size);
868     }
869     mShaperItem.num_glyphs = mShaperItemGlyphArraySize;
870     memset(mShaperItem.offsets, 0, mShaperItem.num_glyphs * sizeof(HB_FixedPoint));
871     return HB_ShapeItem(&mShaperItem);
872 }
873 
createShaperItemGlyphArrays(size_t size)874 void TextLayoutShaper::createShaperItemGlyphArrays(size_t size) {
875 #if DEBUG_GLYPHS
876     ALOGD("Creating Glyph Arrays with size = %d", size);
877 #endif
878     mShaperItemGlyphArraySize = size;
879 
880     // These arrays are all indexed by glyph.
881     mShaperItem.glyphs = new HB_Glyph[size];
882     mShaperItem.attributes = new HB_GlyphAttributes[size];
883     mShaperItem.advances = new HB_Fixed[size];
884     mShaperItem.offsets = new HB_FixedPoint[size];
885 
886     // Although the log_clusters array is indexed by character, Harfbuzz expects that
887     // it is big enough to hold one element per glyph.  So we allocate log_clusters along
888     // with the other glyph arrays above.
889     mShaperItem.log_clusters = new unsigned short[size];
890 }
891 
deleteShaperItemGlyphArrays()892 void TextLayoutShaper::deleteShaperItemGlyphArrays() {
893     delete[] mShaperItem.glyphs;
894     delete[] mShaperItem.attributes;
895     delete[] mShaperItem.advances;
896     delete[] mShaperItem.offsets;
897     delete[] mShaperItem.log_clusters;
898 }
899 
getCachedHBFace(SkTypeface * typeface)900 HB_Face TextLayoutShaper::getCachedHBFace(SkTypeface* typeface) {
901     SkFontID fontId = typeface->uniqueID();
902     ssize_t index = mCachedHBFaces.indexOfKey(fontId);
903     if (index >= 0) {
904         return mCachedHBFaces.valueAt(index);
905     }
906     HB_Face face = HB_NewFace(typeface, harfbuzzSkiaGetTable);
907     if (face) {
908 #if DEBUG_GLYPHS
909         ALOGD("Created HB_NewFace %p from paint typeface = %p", face, typeface);
910 #endif
911         mCachedHBFaces.add(fontId, face);
912     }
913     return face;
914 }
915 
purgeCaches()916 void TextLayoutShaper::purgeCaches() {
917     size_t cacheSize = mCachedHBFaces.size();
918     for (size_t i = 0; i < cacheSize; i++) {
919         HB_FreeFace(mCachedHBFaces.valueAt(i));
920     }
921     mCachedHBFaces.clear();
922     unrefTypefaces();
923     init();
924 }
925 
TextLayoutEngine()926 TextLayoutEngine::TextLayoutEngine() {
927     mShaper = new TextLayoutShaper();
928 #if USE_TEXT_LAYOUT_CACHE
929     mTextLayoutCache = new TextLayoutCache(mShaper);
930 #else
931     mTextLayoutCache = NULL;
932 #endif
933 }
934 
~TextLayoutEngine()935 TextLayoutEngine::~TextLayoutEngine() {
936     delete mTextLayoutCache;
937     delete mShaper;
938 }
939 
getValue(const SkPaint * paint,const jchar * text,jint start,jint count,jint contextCount,jint dirFlags)940 sp<TextLayoutValue> TextLayoutEngine::getValue(const SkPaint* paint, const jchar* text,
941         jint start, jint count, jint contextCount, jint dirFlags) {
942     sp<TextLayoutValue> value;
943 #if USE_TEXT_LAYOUT_CACHE
944     value = mTextLayoutCache->getValue(paint, text, start, count,
945             contextCount, dirFlags);
946     if (value == NULL) {
947         ALOGE("Cannot get TextLayoutCache value for text = '%s'",
948                 String8(text + start, count).string());
949     }
950 #else
951     value = new TextLayoutValue(count);
952     mShaper->computeValues(value.get(), paint,
953             reinterpret_cast<const UChar*>(text), start, count, contextCount, dirFlags);
954 #endif
955     return value;
956 }
957 
purgeCaches()958 void TextLayoutEngine::purgeCaches() {
959 #if USE_TEXT_LAYOUT_CACHE
960     mTextLayoutCache->purgeCaches();
961 #if DEBUG_GLYPHS
962     ALOGD("Purged TextLayoutEngine caches");
963 #endif
964 #endif
965 }
966 
967 
968 } // namespace android
969