TextLayoutCache.cpp revision 689e515ed2b8064c15e54d8ab69d87de54c5e0d6
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#include "TextLayoutCache.h"
18#include "TextLayout.h"
19
20namespace android {
21
22TextLayoutCache::TextLayoutCache() :
23        mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutCacheValue> >::kUnlimitedCapacity),
24        mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)),
25        mCacheHitCount(0), mNanosecondsSaved(0) {
26    init();
27}
28
29TextLayoutCache::TextLayoutCache(uint32_t max):
30        mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutCacheValue> >::kUnlimitedCapacity),
31        mSize(0), mMaxSize(max),
32        mCacheHitCount(0), mNanosecondsSaved(0) {
33    init();
34}
35
36TextLayoutCache::~TextLayoutCache() {
37    mCache.clear();
38}
39
40void TextLayoutCache::init() {
41    mCache.setOnEntryRemovedListener(this);
42
43    mDebugLevel = readRtlDebugLevel();
44    mDebugEnabled = mDebugLevel & kRtlDebugCaches;
45    LOGD("Using TextLayoutCache debug level: %d - Debug Enabled: %d", mDebugLevel, mDebugEnabled);
46
47    mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
48    if (mDebugEnabled) {
49        LOGD("TextLayoutCache start time: %lld", mCacheStartTime);
50    }
51    mInitialized = true;
52
53    if (mDebugEnabled) {
54#if RTL_USE_HARFBUZZ
55        LOGD("TextLayoutCache is using HARFBUZZ");
56#else
57        LOGD("TextLayoutCache is using ICU");
58#endif
59    }
60
61    if (mDebugEnabled) {
62        LOGD("TextLayoutCache initialization is done");
63    }
64}
65
66/*
67 * Size management
68 */
69
70uint32_t TextLayoutCache::getSize() {
71    return mSize;
72}
73
74uint32_t TextLayoutCache::getMaxSize() {
75    return mMaxSize;
76}
77
78void TextLayoutCache::setMaxSize(uint32_t maxSize) {
79    mMaxSize = maxSize;
80    removeOldests();
81}
82
83void TextLayoutCache::removeOldests() {
84    while (mSize > mMaxSize) {
85        mCache.removeOldest();
86    }
87}
88
89/**
90 *  Callbacks
91 */
92void TextLayoutCache::operator()(TextLayoutCacheKey& text, sp<TextLayoutCacheValue>& desc) {
93    if (desc != NULL) {
94        size_t totalSizeToDelete = text.getSize() + desc->getSize();
95        mSize -= totalSizeToDelete;
96        if (mDebugEnabled) {
97            LOGD("Cache value deleted, size = %d", totalSizeToDelete);
98        }
99        desc.clear();
100    }
101}
102
103/*
104 * Cache clearing
105 */
106void TextLayoutCache::clear() {
107    mCache.clear();
108}
109
110/*
111 * Caching
112 */
113sp<TextLayoutCacheValue> TextLayoutCache::getValue(SkPaint* paint,
114            const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) {
115    AutoMutex _l(mLock);
116    nsecs_t startTime = 0;
117    if (mDebugEnabled) {
118        startTime = systemTime(SYSTEM_TIME_MONOTONIC);
119    }
120
121    // Create the key
122    TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags);
123
124    // Get value from cache if possible
125    sp<TextLayoutCacheValue> value = mCache.get(key);
126
127    // Value not found for the key, we need to add a new value in the cache
128    if (value == NULL) {
129        value = new TextLayoutCacheValue();
130
131        // Compute advances and store them
132        value->computeValues(paint, text, start, count, contextCount, dirFlags);
133
134        // Don't bother to add in the cache if the entry is too big
135        size_t size = key.getSize() + value->getSize();
136        if (size <= mMaxSize) {
137            // Cleanup to make some room if needed
138            if (mSize + size > mMaxSize) {
139                if (mDebugEnabled) {
140                    LOGD("TextLayoutCache: need to clean some entries "
141                            "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(systemTime(SYSTEM_TIME_MONOTONIC) - startTime);
159
160                LOGD("CACHE MISS: Added entry for text='%s' with start=%d, count=%d, "
161                        "contextCount=%d, entry size %d bytes, remaining space %d bytes"
162                        " - Compute time in nanos: %d",
163                        String8(text, contextCount).string(), start, count, contextCount,
164                        size, mMaxSize - mSize, value->getElapsedTime());
165            }
166        } else {
167            if (mDebugEnabled) {
168                LOGD("CACHE MISS: Calculated but not storing entry because it is too big "
169                        "for text='%s' with start=%d, count=%d, contextCount=%d, "
170                        "entry size %d bytes, remaining space %d bytes"
171                        " - Compute time in nanos: %d",
172                        String8(text, contextCount).string(), start, count, contextCount,
173                        size, mMaxSize - mSize, value->getElapsedTime());
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 for text='%s' 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",
190                        mCacheHitCount, String8(text, contextCount).string(), start, count,
191                        contextCount,
192                        value->getElapsedTime(), elapsedTimeThruCacheGet, deltaPercent);
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("TextLayoutCache stats");
207    LOGD("------------------------------------------------");
208    LOGD("running   : %.0f seconds", timeRunningInSec);
209    LOGD("size      : %d bytes", mMaxSize);
210    LOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent);
211    LOGD("hits      : %d", mCacheHitCount);
212    LOGD("saved     : %lld milliseconds", mNanosecondsSaved / 1000000);
213    LOGD("------------------------------------------------");
214}
215
216/**
217 * TextLayoutCacheKey
218 */
219TextLayoutCacheKey::TextLayoutCacheKey(): text(NULL), start(0), count(0), contextCount(0),
220        dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0),
221        hinting(SkPaint::kNo_Hinting)  {
222}
223
224TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint,
225        const UChar* text, size_t start, size_t count,
226        size_t contextCount, int dirFlags) :
227            text(text), start(start), count(count), contextCount(contextCount),
228            dirFlags(dirFlags) {
229    typeface = paint->getTypeface();
230    textSize = paint->getTextSize();
231    textSkewX = paint->getTextSkewX();
232    textScaleX = paint->getTextScaleX();
233    flags = paint->getFlags();
234    hinting = paint->getHinting();
235}
236
237bool TextLayoutCacheKey::operator<(const TextLayoutCacheKey& rhs) const {
238    LTE_INT(count) {
239        LTE_INT(contextCount) {
240            LTE_INT(start) {
241                LTE_INT(typeface) {
242                    LTE_FLOAT(textSize) {
243                        LTE_FLOAT(textSkewX) {
244                            LTE_FLOAT(textScaleX) {
245                                LTE_INT(flags) {
246                                    LTE_INT(hinting) {
247                                        LTE_INT(dirFlags) {
248                                            return strncmp16(text, rhs.text, contextCount) < 0;
249                                        }
250                                    }
251                                }
252                            }
253                        }
254                    }
255                }
256            }
257        }
258    }
259    return false;
260}
261
262void TextLayoutCacheKey::internalTextCopy() {
263    textCopy.setTo(text, contextCount);
264    text = textCopy.string();
265}
266
267size_t TextLayoutCacheKey::getSize() {
268    return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount;
269}
270
271/**
272 * TextLayoutCacheValue
273 */
274TextLayoutCacheValue::TextLayoutCacheValue() :
275        mAdvances(NULL), mTotalAdvance(0), mAdvancesCount(0),
276        mGlyphs(NULL), mGlyphsCount(0), mElapsedTime(0) {
277}
278
279TextLayoutCacheValue::~TextLayoutCacheValue() {
280    delete[] mAdvances;
281    delete[] mGlyphs;
282}
283
284void TextLayoutCacheValue::setElapsedTime(uint32_t time) {
285    mElapsedTime = time;
286}
287
288uint32_t TextLayoutCacheValue::getElapsedTime() {
289    return mElapsedTime;
290}
291
292void TextLayoutCacheValue::computeValues(SkPaint* paint, const UChar* chars, size_t start,
293        size_t count, size_t contextCount, int dirFlags) {
294    mAdvancesCount = count;
295    mAdvances = new float[count];
296
297#if RTL_USE_HARFBUZZ
298    computeValuesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags,
299            mAdvances, &mTotalAdvance, &mGlyphs, &mGlyphsCount);
300#else
301    computeAdvancesWithICU(paint, chars, start, count, contextCount, dirFlags,
302            mAdvances, &mTotalAdvance);
303#endif
304#if DEBUG_ADVANCES
305    LOGD("Advances - count=%d - countextCount=%d - totalAdvance=%f - "
306            "adv[0]=%f adv[1]=%f adv[2]=%f adv[3]=%f", count, contextCount, mTotalAdvance,
307            mAdvances[0], mAdvances[1], mAdvances[2], mAdvances[3]);
308#endif
309}
310
311size_t TextLayoutCacheValue::getSize() {
312    return sizeof(TextLayoutCacheValue) + sizeof(jfloat) * mAdvancesCount +
313            sizeof(jchar) * mGlyphsCount;
314}
315
316void TextLayoutCacheValue::setupShaperItem(HB_ShaperItem* shaperItem, HB_FontRec* font,
317        FontData* fontData, SkPaint* paint, const UChar* chars, size_t start, size_t count,
318        size_t contextCount, int dirFlags) {
319    bool isRTL = dirFlags & 0x1;
320
321    font->klass = &harfbuzzSkiaClass;
322    font->userData = 0;
323    // The values which harfbuzzSkiaClass returns are already scaled to
324    // pixel units, so we just set all these to one to disable further
325    // scaling.
326    font->x_ppem = 1;
327    font->y_ppem = 1;
328    font->x_scale = 1;
329    font->y_scale = 1;
330
331    memset(shaperItem, 0, sizeof(*shaperItem));
332    shaperItem->font = font;
333    shaperItem->face = HB_NewFace(shaperItem->font, harfbuzzSkiaGetTable);
334
335    shaperItem->kerning_applied = false;
336
337    // We cannot know, ahead of time, how many glyphs a given script run
338    // will produce. We take a guess that script runs will not produce more
339    // than twice as many glyphs as there are code points plus a bit of
340    // padding and fallback if we find that we are wrong.
341    createGlyphArrays(shaperItem, (contextCount + 2) * 2);
342
343    // Free memory for clusters if needed and recreate the clusters array
344    if (shaperItem->log_clusters) {
345        delete shaperItem->log_clusters;
346    }
347    shaperItem->log_clusters = new unsigned short[contextCount];
348
349    shaperItem->item.pos = start;
350    shaperItem->item.length = count;
351    shaperItem->item.bidiLevel = isRTL;
352    shaperItem->item.script = isRTL ? HB_Script_Arabic : HB_Script_Common;
353
354    shaperItem->string = chars;
355    shaperItem->stringLength = contextCount;
356
357    fontData->typeFace = paint->getTypeface();
358    fontData->textSize = paint->getTextSize();
359    fontData->textSkewX = paint->getTextSkewX();
360    fontData->textScaleX = paint->getTextScaleX();
361    fontData->flags = paint->getFlags();
362    fontData->hinting = paint->getHinting();
363
364    shaperItem->font->userData = fontData;
365}
366
367void TextLayoutCacheValue::shapeWithHarfbuzz(HB_ShaperItem* shaperItem, HB_FontRec* font,
368        FontData* fontData, SkPaint* paint, const UChar* chars, size_t start, size_t count,
369        size_t contextCount, int dirFlags) {
370    // Setup Harfbuzz Shaper
371    setupShaperItem(shaperItem, font, fontData, paint, chars, start, count,
372            contextCount, dirFlags);
373
374    // Shape
375    resetGlyphArrays(shaperItem);
376    while (!HB_ShapeItem(shaperItem)) {
377        // We overflowed our arrays. Resize and retry.
378        // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size.
379        deleteGlyphArrays(shaperItem);
380        createGlyphArrays(shaperItem, shaperItem->num_glyphs << 1);
381        resetGlyphArrays(shaperItem);
382    }
383}
384
385struct GlyphRun {
386    inline GlyphRun() {}
387    inline GlyphRun(jchar* glyphs, size_t glyphsCount, bool isRTL) :
388            glyphs(glyphs), glyphsCount(glyphsCount), isRTL(isRTL) { }
389    jchar* glyphs;
390    size_t glyphsCount;
391    int isRTL;
392};
393
394void TextLayoutCacheValue::computeValuesWithHarfbuzz(SkPaint* paint, const UChar* chars,
395        size_t start, size_t count, size_t contextCount, int dirFlags,
396        jfloat* outAdvances, jfloat* outTotalAdvance,
397        jchar** outGlyphs, size_t* outGlyphsCount) {
398
399        UBiDiLevel bidiReq = 0;
400        bool forceLTR = false;
401        bool forceRTL = false;
402
403        switch (dirFlags) {
404            case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
405            case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
406            case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
407            case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
408            case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR
409            case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL
410        }
411
412        if (forceLTR || forceRTL) {
413#if DEBUG_GLYPHS
414                    LOGD("computeValuesWithHarfbuzz -- forcing run with LTR=%d RTL=%d",
415                            forceLTR, forceRTL);
416#endif
417            computeRunValuesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags,
418                    outAdvances, outTotalAdvance, outGlyphs, outGlyphsCount);
419        } else {
420            UBiDi* bidi = ubidi_open();
421            if (bidi) {
422                UErrorCode status = U_ZERO_ERROR;
423                LOGD("computeValuesWithHarfbuzz -- bidiReq=%d", bidiReq);
424                ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
425                if (U_SUCCESS(status)) {
426                    int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
427                    size_t rc = ubidi_countRuns(bidi, &status);
428#if DEBUG_GLYPHS
429                    LOGD("computeValuesWithHarfbuzz -- dirFlags=%d run-count=%d paraDir=%d", dirFlags, rc, paraDir);
430#endif
431
432                    if (rc == 1 || !U_SUCCESS(status)) {
433                        LOGD("HERE !!!");
434                        computeRunValuesWithHarfbuzz(paint, chars, start, count, contextCount,
435                                dirFlags, outAdvances, outTotalAdvance, outGlyphs, outGlyphsCount);
436                        ubidi_close(bidi);
437                        return;
438                    }
439
440                    size_t runIndex = 0;
441                    Vector<GlyphRun> glyphRuns;
442                    for (size_t i = 0; i < rc; ++i) {
443                        int32_t startRun;
444                        int32_t lengthRun;
445                        UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
446
447                        int newFlags = (runDir == UBIDI_RTL) ? kDirection_RTL : kDirection_LTR;
448                        jfloat runTotalAdvance = 0;
449                        jchar* runGlyphs;
450                        size_t runGlyphsCount = 0;
451
452#if DEBUG_GLYPHS
453                        LOGD("computeValuesWithHarfbuzz -- run-start=%d run-len=%d newFlags=%d",
454                                startRun, lengthRun, newFlags);
455#endif
456                        computeRunValuesWithHarfbuzz(paint, chars, startRun,
457                                lengthRun, contextCount, newFlags,
458                                outAdvances + runIndex, &runTotalAdvance,
459                                &runGlyphs, &runGlyphsCount);
460
461                        runIndex += lengthRun;
462
463                        *outTotalAdvance += runTotalAdvance;
464                        *outGlyphsCount += runGlyphsCount;
465
466#if DEBUG_GLYPHS
467                        LOGD("computeValuesWithHarfbuzz -- run=%d run-glyphs-count=%d",
468                                i, runGlyphsCount);
469                        for (size_t j = 0; j < runGlyphsCount; j++) {
470                            LOGD("                          -- glyphs[%d]=%d", j, runGlyphs[j]);
471                        }
472#endif
473                        glyphRuns.push(GlyphRun(runGlyphs, runGlyphsCount, newFlags));
474                    }
475
476#if DEBUG_GLYPHS
477                    LOGD("computeValuesWithHarfbuzz -- total-glyphs-count=%d", *outGlyphsCount);
478#endif
479                    *outGlyphs = new jchar[*outGlyphsCount];
480                    jchar* glyphs = *outGlyphs;
481                    for (size_t i = 0; i < glyphRuns.size(); i++) {
482                        const GlyphRun& glyphRun = glyphRuns.itemAt(i);
483                        if (glyphRun.isRTL) {
484                            for (size_t n = 0; n < glyphRun.glyphsCount; n++) {
485                                glyphs[glyphRun.glyphsCount - n - 1] = glyphRun.glyphs[n];
486                            }
487                        } else {
488                            memcpy(glyphs, glyphRun.glyphs, glyphRun.glyphsCount * sizeof(jchar));
489                        }
490                        glyphs += glyphRun.glyphsCount;
491                        delete[] glyphRun.glyphs;
492                    }
493                }
494                ubidi_close(bidi);
495            }
496        }
497}
498
499void TextLayoutCacheValue::computeRunValuesWithHarfbuzz(SkPaint* paint, const UChar* chars,
500        size_t start, size_t count, size_t contextCount, int dirFlags,
501        jfloat* outAdvances, jfloat* outTotalAdvance,
502        jchar** outGlyphs, size_t* outGlyphsCount) {
503
504    bool isRTL = dirFlags & 0x1;
505
506    HB_ShaperItem shaperItem;
507    HB_FontRec font;
508    FontData fontData;
509    shapeWithHarfbuzz(&shaperItem, &font, &fontData, paint, chars, start, count,
510            contextCount, dirFlags);
511
512#if DEBUG_GLYPHS
513    LOGD("HARFBUZZ -- num_glypth=%d - kerning_applied=%d", shaperItem.num_glyphs,
514            shaperItem.kerning_applied);
515    LOGD("         -- string= '%s'", String8(chars + start, count).string());
516    LOGD("         -- isDevKernText=%d", paint->isDevKernText());
517#endif
518
519    // Get Advances and their total
520    jfloat totalAdvance = 0;
521    for (size_t i = 0; i < count; i++) {
522        totalAdvance += outAdvances[i] = HBFixedToFloat(shaperItem.advances[i]);
523#if DEBUG_ADVANCES
524        LOGD("hb-adv = %d - rebased = %f - total = %f", shaperItem.advances[i], outAdvances[i],
525                totalAdvance);
526#endif
527    }
528    *outTotalAdvance = totalAdvance;
529
530    // Get Glyphs
531    if (outGlyphs) {
532        *outGlyphsCount = shaperItem.num_glyphs;
533        *outGlyphs = new jchar[shaperItem.num_glyphs];
534        for (size_t i = 0; i < shaperItem.num_glyphs; i++) {
535            (*outGlyphs)[i] = (jchar) shaperItem.glyphs[i];
536        }
537    }
538
539    // Cleaning
540    deleteGlyphArrays(&shaperItem);
541    HB_FreeFace(shaperItem.face);
542}
543
544void TextLayoutCacheValue::computeAdvancesWithICU(SkPaint* paint, const UChar* chars,
545        size_t start, size_t count, size_t contextCount, int dirFlags,
546        jfloat* outAdvances, jfloat* outTotalAdvance) {
547    SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount);
548    jchar* buffer = tempBuffer.get();
549    SkScalar* scalarArray = (SkScalar*)outAdvances;
550
551    // this is where we'd call harfbuzz
552    // for now we just use ushape.c
553    size_t widths;
554    const jchar* text;
555    if (dirFlags & 0x1) { // rtl, call arabic shaping in case
556        UErrorCode status = U_ZERO_ERROR;
557        // Use fixed length since we need to keep start and count valid
558        u_shapeArabic(chars, contextCount, buffer, contextCount,
559                U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
560                U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
561                U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
562        // we shouldn't fail unless there's an out of memory condition,
563        // in which case we're hosed anyway
564        for (int i = start, e = i + count; i < e; ++i) {
565            if (buffer[i] == UNICODE_NOT_A_CHAR) {
566                buffer[i] = UNICODE_ZWSP; // zero-width-space for skia
567            }
568        }
569        text = buffer + start;
570        widths = paint->getTextWidths(text, count << 1, scalarArray);
571    } else {
572        text = chars + start;
573        widths = paint->getTextWidths(text, count << 1, scalarArray);
574    }
575
576    jfloat totalAdvance = 0;
577    if (widths < count) {
578#if DEBUG_ADVANCES
579    LOGD("ICU -- count=%d", widths);
580#endif
581        // Skia operates on code points, not code units, so surrogate pairs return only
582        // one value. Expand the result so we have one value per UTF-16 code unit.
583
584        // Note, skia's getTextWidth gets confused if it encounters a surrogate pair,
585        // leaving the remaining widths zero.  Not nice.
586        for (size_t i = 0, p = 0; i < widths; ++i) {
587            totalAdvance += outAdvances[p++] = SkScalarToFloat(scalarArray[i]);
588            if (p < count &&
589                    text[p] >= UNICODE_FIRST_LOW_SURROGATE &&
590                    text[p] < UNICODE_FIRST_PRIVATE_USE &&
591                    text[p-1] >= UNICODE_FIRST_HIGH_SURROGATE &&
592                    text[p-1] < UNICODE_FIRST_LOW_SURROGATE) {
593                outAdvances[p++] = 0;
594            }
595#if DEBUG_ADVANCES
596            LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance);
597#endif
598        }
599    } else {
600#if DEBUG_ADVANCES
601    LOGD("ICU -- count=%d", count);
602#endif
603        for (size_t i = 0; i < count; i++) {
604            totalAdvance += outAdvances[i] = SkScalarToFloat(scalarArray[i]);
605#if DEBUG_ADVANCES
606            LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance);
607#endif
608        }
609    }
610    *outTotalAdvance = totalAdvance;
611}
612
613void TextLayoutCacheValue::deleteGlyphArrays(HB_ShaperItem* shaperItem) {
614    delete[] shaperItem->glyphs;
615    delete[] shaperItem->attributes;
616    delete[] shaperItem->advances;
617    delete[] shaperItem->offsets;
618}
619
620void TextLayoutCacheValue::createGlyphArrays(HB_ShaperItem* shaperItem, int size) {
621    shaperItem->glyphs = new HB_Glyph[size];
622    shaperItem->attributes = new HB_GlyphAttributes[size];
623    shaperItem->advances = new HB_Fixed[size];
624    shaperItem->offsets = new HB_FixedPoint[size];
625    shaperItem->num_glyphs = size;
626}
627
628void TextLayoutCacheValue::resetGlyphArrays(HB_ShaperItem* shaperItem) {
629    int size = shaperItem->num_glyphs;
630    // All the types here don't have pointers. It is safe to reset to
631    // zero unless Harfbuzz breaks the compatibility in the future.
632    memset(shaperItem->glyphs, 0, size * sizeof(shaperItem->glyphs[0]));
633    memset(shaperItem->attributes, 0, size * sizeof(shaperItem->attributes[0]));
634    memset(shaperItem->advances, 0, size * sizeof(shaperItem->advances[0]));
635    memset(shaperItem->offsets, 0, size * sizeof(shaperItem->offsets[0]));
636}
637
638
639} // namespace android
640