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