• 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 
22 extern "C" {
23   #include "harfbuzz-unicode.h"
24 }
25 
26 namespace android {
27 
28 //--------------------------------------------------------------------------------------------------
29 #if USE_TEXT_LAYOUT_CACHE
30     ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutCache);
31 #endif
32 //--------------------------------------------------------------------------------------------------
33 
TextLayoutCache()34 TextLayoutCache::TextLayoutCache() :
35         mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutCacheValue> >::kUnlimitedCapacity),
36         mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)),
37         mCacheHitCount(0), mNanosecondsSaved(0) {
38     init();
39 }
40 
~TextLayoutCache()41 TextLayoutCache::~TextLayoutCache() {
42     mCache.clear();
43 }
44 
init()45 void TextLayoutCache::init() {
46     mCache.setOnEntryRemovedListener(this);
47 
48     mDebugLevel = readRtlDebugLevel();
49     mDebugEnabled = mDebugLevel & kRtlDebugCaches;
50     LOGD("Using debug level: %d - Debug Enabled: %d", mDebugLevel, mDebugEnabled);
51 
52     mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
53 
54     if (mDebugEnabled) {
55         LOGD("Initialization is done - Start time: %lld", mCacheStartTime);
56     }
57 
58     mInitialized = true;
59 }
60 
61 /*
62  * Size management
63  */
64 
getSize()65 uint32_t TextLayoutCache::getSize() {
66     return mSize;
67 }
68 
getMaxSize()69 uint32_t TextLayoutCache::getMaxSize() {
70     return mMaxSize;
71 }
72 
setMaxSize(uint32_t maxSize)73 void TextLayoutCache::setMaxSize(uint32_t maxSize) {
74     mMaxSize = maxSize;
75     removeOldests();
76 }
77 
removeOldests()78 void TextLayoutCache::removeOldests() {
79     while (mSize > mMaxSize) {
80         mCache.removeOldest();
81     }
82 }
83 
84 /**
85  *  Callbacks
86  */
operator ()(TextLayoutCacheKey & text,sp<TextLayoutCacheValue> & desc)87 void TextLayoutCache::operator()(TextLayoutCacheKey& text, sp<TextLayoutCacheValue>& desc) {
88     if (desc != NULL) {
89         size_t totalSizeToDelete = text.getSize() + desc->getSize();
90         mSize -= totalSizeToDelete;
91         if (mDebugEnabled) {
92             LOGD("Cache value deleted, size = %d", totalSizeToDelete);
93         }
94         desc.clear();
95     }
96 }
97 
98 /*
99  * Cache clearing
100  */
clear()101 void TextLayoutCache::clear() {
102     mCache.clear();
103 }
104 
105 /*
106  * Caching
107  */
getValue(SkPaint * paint,const jchar * text,jint start,jint count,jint contextCount,jint dirFlags)108 sp<TextLayoutCacheValue> TextLayoutCache::getValue(SkPaint* paint,
109             const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) {
110     AutoMutex _l(mLock);
111     nsecs_t startTime = 0;
112     if (mDebugEnabled) {
113         startTime = systemTime(SYSTEM_TIME_MONOTONIC);
114     }
115 
116     // Create the key
117     TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags);
118 
119     // Get value from cache if possible
120     sp<TextLayoutCacheValue> value = mCache.get(key);
121 
122     // Value not found for the key, we need to add a new value in the cache
123     if (value == NULL) {
124         if (mDebugEnabled) {
125             startTime = systemTime(SYSTEM_TIME_MONOTONIC);
126         }
127 
128         value = new TextLayoutCacheValue();
129 
130         // Compute advances and store them
131         value->computeValues(paint, text, start, count, contextCount, dirFlags);
132 
133         nsecs_t endTime = systemTime(SYSTEM_TIME_MONOTONIC);
134 
135         // Don't bother to add in the cache if the entry is too big
136         size_t size = key.getSize() + value->getSize();
137         if (size <= mMaxSize) {
138             // Cleanup to make some room if needed
139             if (mSize + size > mMaxSize) {
140                 if (mDebugEnabled) {
141                     LOGD("Need to clean some entries for making some room for a new entry");
142                 }
143                 while (mSize + size > mMaxSize) {
144                     // This will call the callback
145                     mCache.removeOldest();
146                 }
147             }
148 
149             // Update current cache size
150             mSize += size;
151 
152             // Copy the text when we insert the new entry
153             key.internalTextCopy();
154             mCache.put(key, value);
155 
156             if (mDebugEnabled) {
157                 // Update timing information for statistics
158                 value->setElapsedTime(endTime - startTime);
159 
160                 LOGD("CACHE MISS: Added entry with "
161                         "count=%d, entry size %d bytes, remaining space %d bytes"
162                         " - Compute time in nanos: %d - Text='%s' ",
163                         count, size, mMaxSize - mSize, value->getElapsedTime(),
164                         String8(text, count).string());
165             }
166         } else {
167             if (mDebugEnabled) {
168                 LOGD("CACHE MISS: Calculated but not storing entry because it is too big "
169                         "with start=%d count=%d contextCount=%d, "
170                         "entry size %d bytes, remaining space %d bytes"
171                         " - Compute time in nanos: %lld - Text='%s'",
172                         start, count, contextCount, size, mMaxSize - mSize, endTime,
173                         String8(text, count).string());
174             }
175             value.clear();
176         }
177     } else {
178         // This is a cache hit, just log timestamp and user infos
179         if (mDebugEnabled) {
180             nsecs_t elapsedTimeThruCacheGet = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
181             mNanosecondsSaved += (value->getElapsedTime() - elapsedTimeThruCacheGet);
182             ++mCacheHitCount;
183 
184             if (value->getElapsedTime() > 0) {
185                 float deltaPercent = 100 * ((value->getElapsedTime() - elapsedTimeThruCacheGet)
186                         / ((float)value->getElapsedTime()));
187                 LOGD("CACHE HIT #%d with start=%d count=%d contextCount=%d"
188                         "- Compute time in nanos: %d - "
189                         "Cache get time in nanos: %lld - Gain in percent: %2.2f - Text='%s' ",
190                         mCacheHitCount, start, count, contextCount,
191                         value->getElapsedTime(), elapsedTimeThruCacheGet, deltaPercent,
192                         String8(text, count).string());
193             }
194             if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) {
195                 dumpCacheStats();
196             }
197         }
198     }
199     return value;
200 }
201 
dumpCacheStats()202 void TextLayoutCache::dumpCacheStats() {
203     float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize));
204     float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000;
205     LOGD("------------------------------------------------");
206     LOGD("Cache stats");
207     LOGD("------------------------------------------------");
208     LOGD("pid       : %d", getpid());
209     LOGD("running   : %.0f seconds", timeRunningInSec);
210     LOGD("entries   : %d", mCache.size());
211     LOGD("size      : %d bytes", mMaxSize);
212     LOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent);
213     LOGD("hits      : %d", mCacheHitCount);
214     LOGD("saved     : %lld milliseconds", mNanosecondsSaved / 1000000);
215     LOGD("------------------------------------------------");
216 }
217 
218 /**
219  * TextLayoutCacheKey
220  */
TextLayoutCacheKey()221 TextLayoutCacheKey::TextLayoutCacheKey(): text(NULL), start(0), count(0), contextCount(0),
222         dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0),
223         hinting(SkPaint::kNo_Hinting)  {
224 }
225 
TextLayoutCacheKey(const SkPaint * paint,const UChar * text,size_t start,size_t count,size_t contextCount,int dirFlags)226 TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text,
227         size_t start, size_t count, size_t contextCount, int dirFlags) :
228             text(text), start(start), count(count), contextCount(contextCount),
229             dirFlags(dirFlags) {
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 }
237 
TextLayoutCacheKey(const TextLayoutCacheKey & other)238 TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) :
239         text(NULL),
240         textCopy(other.textCopy),
241         start(other.start),
242         count(other.count),
243         contextCount(other.contextCount),
244         dirFlags(other.dirFlags),
245         typeface(other.typeface),
246         textSize(other.textSize),
247         textSkewX(other.textSkewX),
248         textScaleX(other.textScaleX),
249         flags(other.flags),
250         hinting(other.hinting) {
251     if (other.text) {
252         textCopy.setTo(other.text, other.contextCount);
253     }
254 }
255 
compare(const TextLayoutCacheKey & lhs,const TextLayoutCacheKey & rhs)256 int TextLayoutCacheKey::compare(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) {
257     int deltaInt = lhs.start - rhs.start;
258     if (deltaInt != 0) return (deltaInt);
259 
260     deltaInt = lhs.count - rhs.count;
261     if (deltaInt != 0) return (deltaInt);
262 
263     deltaInt = lhs.contextCount - rhs.contextCount;
264     if (deltaInt != 0) return (deltaInt);
265 
266     if (lhs.typeface < rhs.typeface) return -1;
267     if (lhs.typeface > rhs.typeface) return +1;
268 
269     if (lhs.textSize < rhs.textSize) return -1;
270     if (lhs.textSize > rhs.textSize) return +1;
271 
272     if (lhs.textSkewX < rhs.textSkewX) return -1;
273     if (lhs.textSkewX > rhs.textSkewX) return +1;
274 
275     if (lhs.textScaleX < rhs.textScaleX) return -1;
276     if (lhs.textScaleX > rhs.textScaleX) return +1;
277 
278     deltaInt = lhs.flags - rhs.flags;
279     if (deltaInt != 0) return (deltaInt);
280 
281     deltaInt = lhs.hinting - rhs.hinting;
282     if (deltaInt != 0) return (deltaInt);
283 
284     deltaInt = lhs.dirFlags - rhs.dirFlags;
285     if (deltaInt) return (deltaInt);
286 
287     return memcmp(lhs.getText(), rhs.getText(), lhs.contextCount * sizeof(UChar));
288 }
289 
internalTextCopy()290 void TextLayoutCacheKey::internalTextCopy() {
291     textCopy.setTo(text, contextCount);
292     text = NULL;
293 }
294 
getSize()295 size_t TextLayoutCacheKey::getSize() {
296     return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount;
297 }
298 
299 /**
300  * TextLayoutCacheValue
301  */
TextLayoutCacheValue()302 TextLayoutCacheValue::TextLayoutCacheValue() :
303         mTotalAdvance(0), mElapsedTime(0) {
304 }
305 
setElapsedTime(uint32_t time)306 void TextLayoutCacheValue::setElapsedTime(uint32_t time) {
307     mElapsedTime = time;
308 }
309 
getElapsedTime()310 uint32_t TextLayoutCacheValue::getElapsedTime() {
311     return mElapsedTime;
312 }
313 
computeValues(SkPaint * paint,const UChar * chars,size_t start,size_t count,size_t contextCount,int dirFlags)314 void TextLayoutCacheValue::computeValues(SkPaint* paint, const UChar* chars,
315         size_t start, size_t count, size_t contextCount, int dirFlags) {
316     // Give a hint for advances, glyphs and log clusters vectors size
317     mAdvances.setCapacity(contextCount);
318     mGlyphs.setCapacity(contextCount);
319 
320     computeValuesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags,
321             &mAdvances, &mTotalAdvance, &mGlyphs);
322 #if DEBUG_ADVANCES
323     LOGD("Advances - start=%d, count=%d, countextCount=%d, totalAdvance=%f", start, count,
324             contextCount, mTotalAdvance);
325 #endif
326 }
327 
getSize()328 size_t TextLayoutCacheValue::getSize() {
329     return sizeof(TextLayoutCacheValue) + sizeof(jfloat) * mAdvances.capacity() +
330             sizeof(jchar) * mGlyphs.capacity();
331 }
332 
initShaperItem(HB_ShaperItem & shaperItem,HB_FontRec * font,FontData * fontData,SkPaint * paint,const UChar * chars,size_t contextCount)333 void TextLayoutCacheValue::initShaperItem(HB_ShaperItem& shaperItem, HB_FontRec* font,
334         FontData* fontData, SkPaint* paint, const UChar* chars, size_t contextCount) {
335     // Zero the Shaper struct
336     memset(&shaperItem, 0, sizeof(shaperItem));
337 
338     font->klass = &harfbuzzSkiaClass;
339     font->userData = 0;
340 
341     // The values which harfbuzzSkiaClass returns are already scaled to
342     // pixel units, so we just set all these to one to disable further
343     // scaling.
344     font->x_ppem = 1;
345     font->y_ppem = 1;
346     font->x_scale = 1;
347     font->y_scale = 1;
348 
349     shaperItem.font = font;
350     shaperItem.face = HB_NewFace(shaperItem.font, harfbuzzSkiaGetTable);
351 
352     // Reset kerning
353     shaperItem.kerning_applied = false;
354 
355     // Define font data
356     fontData->typeFace = paint->getTypeface();
357     fontData->textSize = paint->getTextSize();
358     fontData->textSkewX = paint->getTextSkewX();
359     fontData->textScaleX = paint->getTextScaleX();
360     fontData->flags = paint->getFlags();
361     fontData->hinting = paint->getHinting();
362 
363     shaperItem.font->userData = fontData;
364 
365     // We cannot know, ahead of time, how many glyphs a given script run
366     // will produce. We take a guess that script runs will not produce more
367     // than twice as many glyphs as there are code points plus a bit of
368     // padding and fallback if we find that we are wrong.
369     createGlyphArrays(shaperItem, (contextCount + 2) * 2);
370 
371     // Create log clusters array
372     shaperItem.log_clusters = new unsigned short[contextCount];
373 
374     // Set the string properties
375     shaperItem.string = chars;
376     shaperItem.stringLength = contextCount;
377 }
378 
freeShaperItem(HB_ShaperItem & shaperItem)379 void TextLayoutCacheValue::freeShaperItem(HB_ShaperItem& shaperItem) {
380     deleteGlyphArrays(shaperItem);
381     delete[] shaperItem.log_clusters;
382     HB_FreeFace(shaperItem.face);
383 }
384 
shapeRun(HB_ShaperItem & shaperItem,size_t start,size_t count,bool isRTL)385 void TextLayoutCacheValue::shapeRun(HB_ShaperItem& shaperItem, size_t start, size_t count,
386         bool isRTL) {
387     // Update Harfbuzz Shaper
388     shaperItem.item.pos = start;
389     shaperItem.item.length = count;
390     shaperItem.item.bidiLevel = isRTL;
391 
392     shaperItem.item.script = isRTL ? HB_Script_Arabic : HB_Script_Common;
393 
394     // Shape
395     while (!HB_ShapeItem(&shaperItem)) {
396         // We overflowed our arrays. Resize and retry.
397         // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size.
398         deleteGlyphArrays(shaperItem);
399         createGlyphArrays(shaperItem, shaperItem.num_glyphs << 1);
400     }
401 }
402 
computeValuesWithHarfbuzz(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)403 void TextLayoutCacheValue::computeValuesWithHarfbuzz(SkPaint* paint, const UChar* chars,
404         size_t start, size_t count, size_t contextCount, int dirFlags,
405         Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
406         Vector<jchar>* const outGlyphs) {
407 
408         UBiDiLevel bidiReq = 0;
409         bool forceLTR = false;
410         bool forceRTL = false;
411 
412         switch (dirFlags) {
413             case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
414             case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
415             case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
416             case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
417             case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR
418             case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL
419         }
420 
421         HB_ShaperItem shaperItem;
422         HB_FontRec font;
423         FontData fontData;
424 
425         // Initialize Harfbuzz Shaper
426         initShaperItem(shaperItem, &font, &fontData, paint, chars, contextCount);
427 
428         bool useSingleRun = false;
429         bool isRTL = forceRTL;
430         if (forceLTR || forceRTL) {
431             useSingleRun = true;
432         } else {
433             UBiDi* bidi = ubidi_open();
434             if (bidi) {
435                 UErrorCode status = U_ZERO_ERROR;
436 #if DEBUG_GLYPHS
437                 LOGD("computeValuesWithHarfbuzz -- bidiReq=%d", bidiReq);
438 #endif
439                 ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
440                 if (U_SUCCESS(status)) {
441                     int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
442                     ssize_t rc = ubidi_countRuns(bidi, &status);
443 #if DEBUG_GLYPHS
444                     LOGD("computeValuesWithHarfbuzz -- dirFlags=%d run-count=%d paraDir=%d",
445                             dirFlags, rc, paraDir);
446 #endif
447                     if (U_SUCCESS(status) && rc == 1) {
448                         // Normal case: one run, status is ok
449                         isRTL = (paraDir == 1);
450                         useSingleRun = true;
451                     } else if (!U_SUCCESS(status) || rc < 1) {
452                         LOGW("computeValuesWithHarfbuzz -- need to force to single run");
453                         isRTL = (paraDir == 1);
454                         useSingleRun = true;
455                     } else {
456                         int32_t end = start + count;
457                         for (size_t i = 0; i < size_t(rc); ++i) {
458                             int32_t startRun = -1;
459                             int32_t lengthRun = -1;
460                             UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
461 
462                             if (startRun == -1 || lengthRun == -1) {
463                                 // Something went wrong when getting the visual run, need to clear
464                                 // already computed data before doing a single run pass
465                                 LOGW("computeValuesWithHarfbuzz -- visual run is not valid");
466                                 outGlyphs->clear();
467                                 outAdvances->clear();
468                                 *outTotalAdvance = 0;
469                                 isRTL = (paraDir == 1);
470                                 useSingleRun = true;
471                                 break;
472                             }
473 
474                             if (startRun >= end) {
475                                 continue;
476                             }
477                             int32_t endRun = startRun + lengthRun;
478                             if (endRun <= int32_t(start)) {
479                                 continue;
480                             }
481                             if (startRun < int32_t(start)) {
482                                 startRun = int32_t(start);
483                             }
484                             if (endRun > end) {
485                                 endRun = end;
486                             }
487 
488                             lengthRun = endRun - startRun;
489                             isRTL = (runDir == UBIDI_RTL);
490                             jfloat runTotalAdvance = 0;
491 #if DEBUG_GLYPHS
492                             LOGD("computeValuesWithHarfbuzz -- run-start=%d run-len=%d isRTL=%d",
493                                     startRun, lengthRun, isRTL);
494 #endif
495                             computeRunValuesWithHarfbuzz(shaperItem, paint,
496                                     startRun, lengthRun, isRTL,
497                                     outAdvances, &runTotalAdvance, outGlyphs);
498 
499                             *outTotalAdvance += runTotalAdvance;
500                         }
501                     }
502                 } else {
503                     LOGW("computeValuesWithHarfbuzz -- cannot set Para");
504                     useSingleRun = true;
505                     isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
506                 }
507                 ubidi_close(bidi);
508             } else {
509                 LOGW("computeValuesWithHarfbuzz -- cannot ubidi_open()");
510                 useSingleRun = true;
511                 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
512             }
513         }
514 
515         // Default single run case
516         if (useSingleRun){
517 #if DEBUG_GLYPHS
518             LOGD("computeValuesWithHarfbuzz -- Using a SINGLE Run "
519                     "-- run-start=%d run-len=%d isRTL=%d", start, count, isRTL);
520 #endif
521             computeRunValuesWithHarfbuzz(shaperItem, paint,
522                     start, count, isRTL,
523                     outAdvances, outTotalAdvance, outGlyphs);
524         }
525 
526         // Cleaning
527         freeShaperItem(shaperItem);
528 
529 #if DEBUG_GLYPHS
530         LOGD("computeValuesWithHarfbuzz -- total-glyphs-count=%d", outGlyphs->size());
531 #endif
532 }
533 
logGlyphs(HB_ShaperItem shaperItem)534 static void logGlyphs(HB_ShaperItem shaperItem) {
535     LOGD("Got glyphs - count=%d", shaperItem.num_glyphs);
536     for (size_t i = 0; i < shaperItem.num_glyphs; i++) {
537         LOGD("      glyph[%d]=%d - offset.x=%f offset.y=%f", i, shaperItem.glyphs[i],
538                 HBFixedToFloat(shaperItem.offsets[i].x),
539                 HBFixedToFloat(shaperItem.offsets[i].y));
540     }
541 }
542 
computeRunValuesWithHarfbuzz(HB_ShaperItem & shaperItem,SkPaint * paint,size_t start,size_t count,bool isRTL,Vector<jfloat> * const outAdvances,jfloat * outTotalAdvance,Vector<jchar> * const outGlyphs)543 void TextLayoutCacheValue::computeRunValuesWithHarfbuzz(HB_ShaperItem& shaperItem, SkPaint* paint,
544         size_t start, size_t count, bool isRTL,
545         Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
546         Vector<jchar>* const outGlyphs) {
547 
548     shapeRun(shaperItem, start, count, isRTL);
549 
550 #if DEBUG_GLYPHS
551     LOGD("HARFBUZZ -- num_glypth=%d - kerning_applied=%d", shaperItem.num_glyphs,
552             shaperItem.kerning_applied);
553     LOGD("         -- string= '%s'", String8(chars + start, count).string());
554     LOGD("         -- isDevKernText=%d", paint->isDevKernText());
555 
556     logGlyphs(shaperItem);
557 #endif
558 
559     if (shaperItem.advances == NULL || shaperItem.num_glyphs == 0) {
560 #if DEBUG_GLYPHS
561     LOGD("HARFBUZZ -- advances array is empty or num_glypth = 0");
562 #endif
563         outAdvances->insertAt(0, outAdvances->size(), count);
564         *outTotalAdvance = 0;
565         return;
566     }
567 
568     // Get Advances and their total
569     jfloat currentAdvance = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[0]]);
570     jfloat totalAdvance = currentAdvance;
571     outAdvances->add(currentAdvance);
572     for (size_t i = 1; i < count; i++) {
573         size_t clusterPrevious = shaperItem.log_clusters[i - 1];
574         size_t cluster = shaperItem.log_clusters[i];
575         if (cluster == clusterPrevious) {
576             outAdvances->add(0);
577         } else {
578             currentAdvance = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[i]]);
579             totalAdvance += currentAdvance;
580             outAdvances->add(currentAdvance);
581         }
582     }
583     *outTotalAdvance = totalAdvance;
584 
585 #if DEBUG_ADVANCES
586     for (size_t i = 0; i < count; i++) {
587         LOGD("hb-adv[%d] = %f - log_clusters = %d - total = %f", i,
588                 (*outAdvances)[i], shaperItem.log_clusters[i], totalAdvance);
589     }
590 #endif
591 
592     // Get Glyphs and reverse them in place if RTL
593     if (outGlyphs) {
594         size_t countGlyphs = shaperItem.num_glyphs;
595         for (size_t i = 0; i < countGlyphs; i++) {
596             jchar glyph = (jchar) shaperItem.glyphs[(!isRTL) ? i : countGlyphs - 1 - i];
597 #if DEBUG_GLYPHS
598             LOGD("HARFBUZZ  -- glyph[%d]=%d", i, glyph);
599 #endif
600             outGlyphs->add(glyph);
601         }
602     }
603 }
604 
deleteGlyphArrays(HB_ShaperItem & shaperItem)605 void TextLayoutCacheValue::deleteGlyphArrays(HB_ShaperItem& shaperItem) {
606     delete[] shaperItem.glyphs;
607     delete[] shaperItem.attributes;
608     delete[] shaperItem.advances;
609     delete[] shaperItem.offsets;
610 }
611 
createGlyphArrays(HB_ShaperItem & shaperItem,int size)612 void TextLayoutCacheValue::createGlyphArrays(HB_ShaperItem& shaperItem, int size) {
613     shaperItem.glyphs = new HB_Glyph[size];
614     shaperItem.attributes = new HB_GlyphAttributes[size];
615     shaperItem.advances = new HB_Fixed[size];
616     shaperItem.offsets = new HB_FixedPoint[size];
617     shaperItem.num_glyphs = size;
618 }
619 
620 } // namespace android
621