TextLayoutCache.cpp revision 54dc642cc1e188b9eeecadb648b9e8c610f4b857
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
22extern "C" {
23  #include "harfbuzz-unicode.h"
24}
25
26namespace android {
27
28//--------------------------------------------------------------------------------------------------
29#if USE_TEXT_LAYOUT_CACHE
30    ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutCache);
31#endif
32//--------------------------------------------------------------------------------------------------
33
34TextLayoutCache::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
41TextLayoutCache::~TextLayoutCache() {
42    mCache.clear();
43}
44
45void 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
65uint32_t TextLayoutCache::getSize() {
66    return mSize;
67}
68
69uint32_t TextLayoutCache::getMaxSize() {
70    return mMaxSize;
71}
72
73void TextLayoutCache::setMaxSize(uint32_t maxSize) {
74    mMaxSize = maxSize;
75    removeOldests();
76}
77
78void TextLayoutCache::removeOldests() {
79    while (mSize > mMaxSize) {
80        mCache.removeOldest();
81    }
82}
83
84/**
85 *  Callbacks
86 */
87void 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 */
101void TextLayoutCache::clear() {
102    mCache.clear();
103}
104
105/*
106 * Caching
107 */
108sp<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 start=%d, count=%d, "
161                        "contextCount=%d, entry size %d bytes, remaining space %d bytes"
162                        " - Compute time in nanos: %d - Text='%s' ",
163                        start, count, contextCount, size, mMaxSize - mSize, value->getElapsedTime(),
164                        String8(text, contextCount).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, contextCount).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, contextCount).string());
193            }
194            if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) {
195                dumpCacheStats();
196            }
197        }
198    }
199    return value;
200}
201
202void 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 */
221TextLayoutCacheKey::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
226TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint,
227        const UChar* text, size_t start, size_t count,
228        size_t contextCount, int dirFlags) :
229            text(text), start(start), count(count), contextCount(contextCount),
230            dirFlags(dirFlags) {
231    typeface = paint->getTypeface();
232    textSize = paint->getTextSize();
233    textSkewX = paint->getTextSkewX();
234    textScaleX = paint->getTextScaleX();
235    flags = paint->getFlags();
236    hinting = paint->getHinting();
237}
238
239bool TextLayoutCacheKey::operator<(const TextLayoutCacheKey& rhs) const {
240    LTE_INT(count) {
241        LTE_INT(contextCount) {
242            LTE_INT(start) {
243                LTE_INT(typeface) {
244                    LTE_FLOAT(textSize) {
245                        LTE_FLOAT(textSkewX) {
246                            LTE_FLOAT(textScaleX) {
247                                LTE_INT(flags) {
248                                    LTE_INT(hinting) {
249                                        LTE_INT(dirFlags) {
250                                            return strncmp16(text, rhs.text, contextCount) < 0;
251                                        }
252                                    }
253                                }
254                            }
255                        }
256                    }
257                }
258            }
259        }
260    }
261    return false;
262}
263
264void TextLayoutCacheKey::internalTextCopy() {
265    textCopy.setTo(text, contextCount);
266    text = textCopy.string();
267}
268
269size_t TextLayoutCacheKey::getSize() {
270    return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount;
271}
272
273/**
274 * TextLayoutCacheValue
275 */
276TextLayoutCacheValue::TextLayoutCacheValue() :
277        mAdvances(NULL), mTotalAdvance(0), mAdvancesCount(0),
278        mGlyphs(NULL), mGlyphsCount(0), mElapsedTime(0) {
279}
280
281TextLayoutCacheValue::~TextLayoutCacheValue() {
282    delete[] mAdvances;
283    delete[] mGlyphs;
284}
285
286void TextLayoutCacheValue::setElapsedTime(uint32_t time) {
287    mElapsedTime = time;
288}
289
290uint32_t TextLayoutCacheValue::getElapsedTime() {
291    return mElapsedTime;
292}
293
294void TextLayoutCacheValue::computeValues(SkPaint* paint, const UChar* chars, size_t start,
295        size_t count, size_t contextCount, int dirFlags) {
296    mAdvancesCount = count;
297    mAdvances = new float[count];
298
299    computeValuesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags,
300            mAdvances, &mTotalAdvance, &mGlyphs, &mGlyphsCount);
301#if DEBUG_ADVANCES
302    LOGD("Advances - count=%d - countextCount=%d - totalAdvance=%f - "
303            "adv[0]=%f adv[1]=%f adv[2]=%f adv[3]=%f", count, contextCount, mTotalAdvance,
304            mAdvances[0], mAdvances[1], mAdvances[2], mAdvances[3]);
305#endif
306}
307
308size_t TextLayoutCacheValue::getSize() {
309    return sizeof(TextLayoutCacheValue) + sizeof(jfloat) * mAdvancesCount +
310            sizeof(jchar) * mGlyphsCount;
311}
312
313void TextLayoutCacheValue::setupShaperItem(HB_ShaperItem* shaperItem, HB_FontRec* font,
314        FontData* fontData, SkPaint* paint, const UChar* chars, size_t start, size_t count,
315        size_t contextCount, bool isRTL) {
316    font->klass = &harfbuzzSkiaClass;
317    font->userData = 0;
318    // The values which harfbuzzSkiaClass returns are already scaled to
319    // pixel units, so we just set all these to one to disable further
320    // scaling.
321    font->x_ppem = 1;
322    font->y_ppem = 1;
323    font->x_scale = 1;
324    font->y_scale = 1;
325
326    memset(shaperItem, 0, sizeof(*shaperItem));
327    shaperItem->font = font;
328    shaperItem->face = HB_NewFace(shaperItem->font, harfbuzzSkiaGetTable);
329
330    shaperItem->kerning_applied = false;
331
332    // We cannot know, ahead of time, how many glyphs a given script run
333    // will produce. We take a guess that script runs will not produce more
334    // than twice as many glyphs as there are code points plus a bit of
335    // padding and fallback if we find that we are wrong.
336    createGlyphArrays(shaperItem, (contextCount + 2) * 2);
337
338    // Free memory for clusters if needed and recreate the clusters array
339    if (shaperItem->log_clusters) {
340        delete shaperItem->log_clusters;
341    }
342    shaperItem->log_clusters = new unsigned short[contextCount];
343
344    shaperItem->item.pos = start;
345    shaperItem->item.length = count;
346    shaperItem->item.bidiLevel = isRTL;
347
348    shaperItem->item.script = isRTL ? HB_Script_Arabic : HB_Script_Common;
349
350    shaperItem->string = chars;
351    shaperItem->stringLength = contextCount;
352
353    fontData->typeFace = paint->getTypeface();
354    fontData->textSize = paint->getTextSize();
355    fontData->textSkewX = paint->getTextSkewX();
356    fontData->textScaleX = paint->getTextScaleX();
357    fontData->flags = paint->getFlags();
358    fontData->hinting = paint->getHinting();
359
360    shaperItem->font->userData = fontData;
361}
362
363void TextLayoutCacheValue::shapeWithHarfbuzz(HB_ShaperItem* shaperItem, HB_FontRec* font,
364        FontData* fontData, SkPaint* paint, const UChar* chars, size_t start, size_t count,
365        size_t contextCount, bool isRTL) {
366    // Setup Harfbuzz Shaper
367    setupShaperItem(shaperItem, font, fontData, paint, chars, start, count,
368            contextCount, isRTL);
369
370    // Shape
371    resetGlyphArrays(shaperItem);
372    while (!HB_ShapeItem(shaperItem)) {
373        // We overflowed our arrays. Resize and retry.
374        // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size.
375        deleteGlyphArrays(shaperItem);
376        createGlyphArrays(shaperItem, shaperItem->num_glyphs << 1);
377        resetGlyphArrays(shaperItem);
378    }
379}
380
381struct GlyphRun {
382    inline GlyphRun() {}
383    inline GlyphRun(jchar* glyphs, size_t glyphsCount, bool isRTL) :
384            glyphs(glyphs), glyphsCount(glyphsCount), isRTL(isRTL) { }
385    jchar* glyphs;
386    size_t glyphsCount;
387    int isRTL;
388};
389
390void static reverseGlyphArray(jchar* glyphs, size_t count) {
391    for (size_t i = 0; i < count / 2; i++) {
392        jchar temp = glyphs[i];
393        glyphs[i] = glyphs[count - 1 - i];
394        glyphs[count - 1 - i] = temp;
395    }
396}
397
398void TextLayoutCacheValue::computeValuesWithHarfbuzz(SkPaint* paint, const UChar* chars,
399        size_t start, size_t count, size_t contextCount, int dirFlags,
400        jfloat* outAdvances, jfloat* outTotalAdvance,
401        jchar** outGlyphs, size_t* outGlyphsCount) {
402
403        UBiDiLevel bidiReq = 0;
404        bool forceLTR = false;
405        bool forceRTL = false;
406
407        switch (dirFlags) {
408            case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
409            case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
410            case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
411            case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
412            case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR
413            case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL
414        }
415
416        if (forceLTR || forceRTL) {
417#if DEBUG_GLYPHS
418                    LOGD("computeValuesWithHarfbuzz -- forcing run with LTR=%d RTL=%d",
419                            forceLTR, forceRTL);
420#endif
421            computeRunValuesWithHarfbuzz(paint, chars, start, count, contextCount, forceRTL,
422                    outAdvances, outTotalAdvance, outGlyphs, outGlyphsCount);
423
424            if (forceRTL && *outGlyphsCount > 1) {
425                reverseGlyphArray(*outGlyphs, *outGlyphsCount);
426            }
427        } else {
428            UBiDi* bidi = ubidi_open();
429            if (bidi) {
430                UErrorCode status = U_ZERO_ERROR;
431#if DEBUG_GLYPHS
432                LOGD("computeValuesWithHarfbuzz -- bidiReq=%d", bidiReq);
433#endif
434                ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
435                if (U_SUCCESS(status)) {
436                    int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
437                    size_t rc = ubidi_countRuns(bidi, &status);
438#if DEBUG_GLYPHS
439                    LOGD("computeValuesWithHarfbuzz -- dirFlags=%d run-count=%d paraDir=%d", dirFlags, rc, paraDir);
440#endif
441                    if (rc == 1 || !U_SUCCESS(status)) {
442                        bool isRTL = (paraDir == 1);
443#if DEBUG_GLYPHS
444                        LOGD("computeValuesWithHarfbuzz -- processing SINGLE run "
445                                "-- run-start=%d run-len=%d isRTL=%d", start, count, isRTL);
446#endif
447                        computeRunValuesWithHarfbuzz(paint, chars, start, count, contextCount,
448                                isRTL, outAdvances, outTotalAdvance, outGlyphs, outGlyphsCount);
449
450                        if (isRTL && *outGlyphsCount > 1) {
451                            reverseGlyphArray(*outGlyphs, *outGlyphsCount);
452                        }
453                    } else {
454                        Vector<GlyphRun> glyphRuns;
455                        jchar* runGlyphs;
456                        size_t runGlyphsCount = 0;
457                        int32_t end = start + count;
458                        for (size_t i = 0; i < rc; ++i) {
459                            int32_t startRun;
460                            int32_t lengthRun;
461                            UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
462
463                            if (startRun >= end) {
464                              break;
465                            }
466
467                            int32_t endRun = startRun + lengthRun;
468                            if (endRun <= start) {
469                              continue;
470                            }
471
472                            if (startRun < start) {
473                              startRun = start;
474                            }
475                            if (endRun > end) {
476                              endRun = end;
477                            }
478
479                            lengthRun = endRun - startRun;
480
481                            bool isRTL = (runDir == UBIDI_RTL);
482                            jfloat runTotalAdvance = 0;
483#if DEBUG_GLYPHS
484                            LOGD("computeValuesWithHarfbuzz -- run-start=%d run-len=%d isRTL=%d",
485                                    startRun, lengthRun, isRTL);
486#endif
487                            computeRunValuesWithHarfbuzz(paint, chars, startRun,
488                                    lengthRun, contextCount, isRTL,
489                                    outAdvances, &runTotalAdvance,
490                                    &runGlyphs, &runGlyphsCount);
491
492                            outAdvances += lengthRun;
493                            *outTotalAdvance += runTotalAdvance;
494                            *outGlyphsCount += runGlyphsCount;
495#if DEBUG_GLYPHS
496                            LOGD("computeValuesWithHarfbuzz -- run=%d run-glyphs-count=%d",
497                                    i, runGlyphsCount);
498                            for (size_t j = 0; j < runGlyphsCount; j++) {
499                                LOGD("                          -- glyphs[%d]=%d", j, runGlyphs[j]);
500                            }
501#endif
502                            glyphRuns.push(GlyphRun(runGlyphs, runGlyphsCount, isRTL));
503                        }
504                        *outGlyphs = new jchar[*outGlyphsCount];
505
506                        jchar* glyphs = *outGlyphs;
507                        for (size_t i = 0; i < glyphRuns.size(); i++) {
508                            const GlyphRun& glyphRun = glyphRuns.itemAt(i);
509                            if (glyphRun.isRTL) {
510                                for (size_t n = 0; n < glyphRun.glyphsCount; n++) {
511                                    glyphs[glyphRun.glyphsCount - n - 1] = glyphRun.glyphs[n];
512                                }
513                            } else {
514                                memcpy(glyphs, glyphRun.glyphs, glyphRun.glyphsCount * sizeof(jchar));
515                            }
516                            glyphs += glyphRun.glyphsCount;
517                            delete[] glyphRun.glyphs;
518                        }
519                    }
520                }
521                ubidi_close(bidi);
522            } else {
523                // Cannot run BiDi, just consider one Run
524                bool isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
525#if DEBUG_GLYPHS
526                LOGD("computeValuesWithHarfbuzz -- cannot run BiDi, considering a SINGLE Run "
527                        "-- run-start=%d run-len=%d isRTL=%d", start, count, isRTL);
528#endif
529                computeRunValuesWithHarfbuzz(paint, chars, start, count, contextCount, isRTL,
530                        outAdvances, outTotalAdvance, outGlyphs, outGlyphsCount);
531
532                if (isRTL && *outGlyphsCount > 1) {
533                    reverseGlyphArray(*outGlyphs, *outGlyphsCount);
534                }
535            }
536        }
537#if DEBUG_GLYPHS
538        LOGD("computeValuesWithHarfbuzz -- total-glyphs-count=%d", *outGlyphsCount);
539#endif
540}
541
542static void logGlyphs(HB_ShaperItem shaperItem) {
543    LOGD("Got glyphs - count=%d", shaperItem.num_glyphs);
544    for (size_t i = 0; i < shaperItem.num_glyphs; i++) {
545        LOGD("      glyphs[%d]=%d - offset.x=%f offset.y=%f", i, shaperItem.glyphs[i],
546                HBFixedToFloat(shaperItem.offsets[i].x),
547                HBFixedToFloat(shaperItem.offsets[i].y));
548    }
549}
550
551void TextLayoutCacheValue::computeRunValuesWithHarfbuzz(SkPaint* paint, const UChar* chars,
552        size_t start, size_t count, size_t contextCount, bool isRTL,
553        jfloat* outAdvances, jfloat* outTotalAdvance,
554        jchar** outGlyphs, size_t* outGlyphsCount) {
555
556    HB_ShaperItem shaperItem;
557    HB_FontRec font;
558    FontData fontData;
559    shapeWithHarfbuzz(&shaperItem, &font, &fontData, paint, chars, start, count,
560            contextCount, isRTL);
561
562#if DEBUG_GLYPHS
563    LOGD("HARFBUZZ -- num_glypth=%d - kerning_applied=%d", shaperItem.num_glyphs,
564            shaperItem.kerning_applied);
565    LOGD("         -- string= '%s'", String8(chars + start, count).string());
566    LOGD("         -- isDevKernText=%d", paint->isDevKernText());
567
568    logGlyphs(shaperItem);
569#endif
570
571    if (shaperItem.advances == NULL || shaperItem.num_glyphs == 0) {
572#if DEBUG_GLYPHS
573    LOGD("HARFBUZZ -- advances array is empty or num_glypth = 0");
574#endif
575        for (size_t i = 0; i < count; i++) {
576            outAdvances[i] = 0;
577        }
578        *outTotalAdvance = 0;
579
580        if (outGlyphs) {
581            *outGlyphsCount = 0;
582            *outGlyphs = new jchar[0];
583        }
584
585        // Cleaning
586        deleteGlyphArrays(&shaperItem);
587        HB_FreeFace(shaperItem.face);
588        return;
589    }
590    // Get Advances and their total
591    jfloat totalAdvance = outAdvances[0] = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[0]]);
592    for (size_t i = 1; i < count; i++) {
593        size_t clusterPrevious = shaperItem.log_clusters[i - 1];
594        size_t cluster = shaperItem.log_clusters[i];
595        if (cluster == clusterPrevious) {
596            outAdvances[i] = 0;
597        } else {
598            totalAdvance += outAdvances[i] = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[i]]);
599        }
600    }
601    *outTotalAdvance = totalAdvance;
602
603#if DEBUG_ADVANCES
604    for (size_t i = 0; i < count; i++) {
605        LOGD("hb-adv[%d] = %f - log_clusters = %d - total = %f", i,
606                outAdvances[i], shaperItem.log_clusters[i], totalAdvance);
607    }
608#endif
609
610    // Get Glyphs
611    if (outGlyphs) {
612        *outGlyphsCount = shaperItem.num_glyphs;
613        *outGlyphs = new jchar[shaperItem.num_glyphs];
614        for (size_t i = 0; i < shaperItem.num_glyphs; i++) {
615            (*outGlyphs)[i] = (jchar) shaperItem.glyphs[i];
616        }
617    }
618
619    // Cleaning
620    deleteGlyphArrays(&shaperItem);
621    HB_FreeFace(shaperItem.face);
622}
623
624void TextLayoutCacheValue::deleteGlyphArrays(HB_ShaperItem* shaperItem) {
625    delete[] shaperItem->glyphs;
626    delete[] shaperItem->attributes;
627    delete[] shaperItem->advances;
628    delete[] shaperItem->offsets;
629}
630
631void TextLayoutCacheValue::createGlyphArrays(HB_ShaperItem* shaperItem, int size) {
632    shaperItem->glyphs = new HB_Glyph[size];
633    shaperItem->attributes = new HB_GlyphAttributes[size];
634    shaperItem->advances = new HB_Fixed[size];
635    shaperItem->offsets = new HB_FixedPoint[size];
636    shaperItem->num_glyphs = size;
637}
638
639void TextLayoutCacheValue::resetGlyphArrays(HB_ShaperItem* shaperItem) {
640    int size = shaperItem->num_glyphs;
641    // All the types here don't have pointers. It is safe to reset to
642    // zero unless Harfbuzz breaks the compatibility in the future.
643    memset(shaperItem->glyphs, 0, size * sizeof(shaperItem->glyphs[0]));
644    memset(shaperItem->attributes, 0, size * sizeof(shaperItem->attributes[0]));
645    memset(shaperItem->advances, 0, size * sizeof(shaperItem->advances[0]));
646    memset(shaperItem->offsets, 0, size * sizeof(shaperItem->offsets[0]));
647}
648
649
650} // namespace android
651