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