TextLayoutCache.cpp revision a4f5aa87c73de7a2581dc4dd72e0f90ccea79a18
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#include "SkFontHost.h"
22
23extern "C" {
24  #include "harfbuzz-unicode.h"
25}
26
27namespace android {
28
29//--------------------------------------------------------------------------------------------------
30#define TYPEFACE_ARABIC "/system/fonts/DroidNaskh-Regular.ttf"
31#define TYPE_FACE_HEBREW_REGULAR "/system/fonts/DroidSansHebrew-Regular.ttf"
32#define TYPE_FACE_HEBREW_BOLD "/system/fonts/DroidSansHebrew-Bold.ttf"
33
34#if USE_TEXT_LAYOUT_CACHE
35
36    ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutCache);
37
38    static SkTypeface* gDefaultTypeface = SkFontHost::CreateTypeface(
39            NULL, NULL, NULL, 0, SkTypeface::kNormal);
40
41    static SkTypeface* gArabicTypeface = NULL;
42    static SkTypeface* gHebrewRegularTypeface = NULL;
43    static SkTypeface* gHebrewBoldTypeface = NULL;
44
45#endif
46//--------------------------------------------------------------------------------------------------
47
48TextLayoutCache::TextLayoutCache() :
49        mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutCacheValue> >::kUnlimitedCapacity),
50        mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)),
51        mCacheHitCount(0), mNanosecondsSaved(0) {
52    init();
53}
54
55TextLayoutCache::~TextLayoutCache() {
56    mCache.clear();
57}
58
59void TextLayoutCache::init() {
60    mCache.setOnEntryRemovedListener(this);
61
62    mDebugLevel = readRtlDebugLevel();
63    mDebugEnabled = mDebugLevel & kRtlDebugCaches;
64    LOGD("Using debug level: %d - Debug Enabled: %d", mDebugLevel, mDebugEnabled);
65
66    mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
67
68    if (mDebugEnabled) {
69        LOGD("Initialization is done - Start time: %lld", mCacheStartTime);
70    }
71
72    mInitialized = true;
73}
74
75/**
76 *  Callbacks
77 */
78void TextLayoutCache::operator()(TextLayoutCacheKey& text, sp<TextLayoutCacheValue>& desc) {
79    size_t totalSizeToDelete = text.getSize() + desc->getSize();
80    mSize -= totalSizeToDelete;
81    if (mDebugEnabled) {
82        LOGD("Cache value %p deleted, size = %d", desc.get(), totalSizeToDelete);
83    }
84}
85
86/*
87 * Cache clearing
88 */
89void TextLayoutCache::clear() {
90    mCache.clear();
91}
92
93/*
94 * Caching
95 */
96sp<TextLayoutCacheValue> TextLayoutCache::getValue(SkPaint* paint,
97            const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) {
98    AutoMutex _l(mLock);
99    nsecs_t startTime = 0;
100    if (mDebugEnabled) {
101        startTime = systemTime(SYSTEM_TIME_MONOTONIC);
102    }
103
104    // Create the key
105    TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags);
106
107    // Get value from cache if possible
108    sp<TextLayoutCacheValue> value = mCache.get(key);
109
110    // Value not found for the key, we need to add a new value in the cache
111    if (value == NULL) {
112        if (mDebugEnabled) {
113            startTime = systemTime(SYSTEM_TIME_MONOTONIC);
114        }
115
116        value = new TextLayoutCacheValue();
117
118        // Compute advances and store them
119        value->computeValues(paint, text, start, count, contextCount, dirFlags);
120
121        if (mDebugEnabled) {
122            value->setElapsedTime(systemTime(SYSTEM_TIME_MONOTONIC) - startTime);
123        }
124
125        // Don't bother to add in the cache if the entry is too big
126        size_t size = key.getSize() + value->getSize();
127        if (size <= mMaxSize) {
128            // Cleanup to make some room if needed
129            if (mSize + size > mMaxSize) {
130                if (mDebugEnabled) {
131                    LOGD("Need to clean some entries for making some room for a new entry");
132                }
133                while (mSize + size > mMaxSize) {
134                    // This will call the callback
135                    bool removedOne = mCache.removeOldest();
136                    LOG_ALWAYS_FATAL_IF(!removedOne, "The cache is non-empty but we "
137                            "failed to remove the oldest entry.  "
138                            "mSize=%u, size=%u, mMaxSize=%u, mCache.size()=%u",
139                            mSize, size, mMaxSize, mCache.size());
140                }
141            }
142
143            // Update current cache size
144            mSize += size;
145
146            // Copy the text when we insert the new entry
147            key.internalTextCopy();
148
149            bool putOne = mCache.put(key, value);
150            LOG_ALWAYS_FATAL_IF(!putOne, "Failed to put an entry into the cache.  "
151                    "This indicates that the cache already has an entry with the "
152                    "same key but it should not since we checked earlier!"
153                    " - start=%d count=%d contextCount=%d - Text='%s'",
154                    start, count, contextCount, String8(text + start, count).string());
155
156            if (mDebugEnabled) {
157                nsecs_t totalTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
158                LOGD("CACHE MISS: Added entry %p "
159                        "with start=%d count=%d contextCount=%d, "
160                        "entry size %d bytes, remaining space %d bytes"
161                        " - Compute time %0.6f ms - Put time %0.6f ms - Text='%s'",
162                        value.get(), start, count, contextCount, size, mMaxSize - mSize,
163                        value->getElapsedTime() * 0.000001f,
164                        (totalTime - value->getElapsedTime()) * 0.000001f,
165                        String8(text + start, count).string());
166            }
167        } else {
168            if (mDebugEnabled) {
169                LOGD("CACHE MISS: Calculated but not storing entry because it is too big "
170                        "with start=%d count=%d contextCount=%d, "
171                        "entry size %d bytes, remaining space %d bytes"
172                        " - Compute time %0.6f ms - Text='%s'",
173                        start, count, contextCount, size, mMaxSize - mSize,
174                        value->getElapsedTime() * 0.000001f,
175                        String8(text + start, count).string());
176            }
177            value.clear();
178        }
179    } else {
180        // This is a cache hit, just log timestamp and user infos
181        if (mDebugEnabled) {
182            nsecs_t elapsedTimeThruCacheGet = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
183            mNanosecondsSaved += (value->getElapsedTime() - elapsedTimeThruCacheGet);
184            ++mCacheHitCount;
185
186            if (value->getElapsedTime() > 0) {
187                float deltaPercent = 100 * ((value->getElapsedTime() - elapsedTimeThruCacheGet)
188                        / ((float)value->getElapsedTime()));
189                LOGD("CACHE HIT #%d with start=%d count=%d contextCount=%d"
190                        "- Compute time %0.6f ms - "
191                        "Cache get time %0.6f ms - Gain in percent: %2.2f - Text='%s' ",
192                        mCacheHitCount, start, count, contextCount,
193                        value->getElapsedTime() * 0.000001f,
194                        elapsedTimeThruCacheGet * 0.000001f,
195                        deltaPercent,
196                        String8(text, count).string());
197            }
198            if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) {
199                dumpCacheStats();
200            }
201        }
202    }
203    return value;
204}
205
206void TextLayoutCache::dumpCacheStats() {
207    float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize));
208    float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000;
209
210    size_t bytes = 0;
211    size_t cacheSize = mCache.size();
212    for (size_t i = 0; i < cacheSize; i++) {
213        bytes += mCache.getKeyAt(i).getSize() + mCache.getValueAt(i)->getSize();
214    }
215
216    LOGD("------------------------------------------------");
217    LOGD("Cache stats");
218    LOGD("------------------------------------------------");
219    LOGD("pid       : %d", getpid());
220    LOGD("running   : %.0f seconds", timeRunningInSec);
221    LOGD("entries   : %d", cacheSize);
222    LOGD("max size  : %d bytes", mMaxSize);
223    LOGD("used      : %d bytes according to mSize, %d bytes actual", mSize, bytes);
224    LOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent);
225    LOGD("hits      : %d", mCacheHitCount);
226    LOGD("saved     : %0.6f ms", mNanosecondsSaved * 0.000001f);
227    LOGD("------------------------------------------------");
228}
229
230/**
231 * TextLayoutCacheKey
232 */
233TextLayoutCacheKey::TextLayoutCacheKey(): text(NULL), start(0), count(0), contextCount(0),
234        dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0),
235        hinting(SkPaint::kNo_Hinting)  {
236}
237
238TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text,
239        size_t start, size_t count, size_t contextCount, int dirFlags) :
240            text(text), start(start), count(count), contextCount(contextCount),
241            dirFlags(dirFlags) {
242    typeface = paint->getTypeface();
243    textSize = paint->getTextSize();
244    textSkewX = paint->getTextSkewX();
245    textScaleX = paint->getTextScaleX();
246    flags = paint->getFlags();
247    hinting = paint->getHinting();
248}
249
250TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) :
251        text(NULL),
252        textCopy(other.textCopy),
253        start(other.start),
254        count(other.count),
255        contextCount(other.contextCount),
256        dirFlags(other.dirFlags),
257        typeface(other.typeface),
258        textSize(other.textSize),
259        textSkewX(other.textSkewX),
260        textScaleX(other.textScaleX),
261        flags(other.flags),
262        hinting(other.hinting) {
263    if (other.text) {
264        textCopy.setTo(other.text, other.contextCount);
265    }
266}
267
268int TextLayoutCacheKey::compare(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) {
269    int deltaInt = lhs.start - rhs.start;
270    if (deltaInt != 0) return (deltaInt);
271
272    deltaInt = lhs.count - rhs.count;
273    if (deltaInt != 0) return (deltaInt);
274
275    deltaInt = lhs.contextCount - rhs.contextCount;
276    if (deltaInt != 0) return (deltaInt);
277
278    if (lhs.typeface < rhs.typeface) return -1;
279    if (lhs.typeface > rhs.typeface) return +1;
280
281    if (lhs.textSize < rhs.textSize) return -1;
282    if (lhs.textSize > rhs.textSize) return +1;
283
284    if (lhs.textSkewX < rhs.textSkewX) return -1;
285    if (lhs.textSkewX > rhs.textSkewX) return +1;
286
287    if (lhs.textScaleX < rhs.textScaleX) return -1;
288    if (lhs.textScaleX > rhs.textScaleX) return +1;
289
290    deltaInt = lhs.flags - rhs.flags;
291    if (deltaInt != 0) return (deltaInt);
292
293    deltaInt = lhs.hinting - rhs.hinting;
294    if (deltaInt != 0) return (deltaInt);
295
296    deltaInt = lhs.dirFlags - rhs.dirFlags;
297    if (deltaInt) return (deltaInt);
298
299    return memcmp(lhs.getText(), rhs.getText(), lhs.contextCount * sizeof(UChar));
300}
301
302void TextLayoutCacheKey::internalTextCopy() {
303    textCopy.setTo(text, contextCount);
304    text = NULL;
305}
306
307size_t TextLayoutCacheKey::getSize() const {
308    return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount;
309}
310
311/**
312 * TextLayoutCacheValue
313 */
314TextLayoutCacheValue::TextLayoutCacheValue() :
315        mTotalAdvance(0), mElapsedTime(0) {
316}
317
318void TextLayoutCacheValue::setElapsedTime(uint32_t time) {
319    mElapsedTime = time;
320}
321
322uint32_t TextLayoutCacheValue::getElapsedTime() {
323    return mElapsedTime;
324}
325
326void TextLayoutCacheValue::computeValues(SkPaint* paint, const UChar* chars,
327        size_t start, size_t count, size_t contextCount, int dirFlags) {
328    // Give a hint for advances, glyphs and log clusters vectors size
329    mAdvances.setCapacity(contextCount);
330    mGlyphs.setCapacity(contextCount);
331
332    computeValuesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags,
333            &mAdvances, &mTotalAdvance, &mGlyphs);
334#if DEBUG_ADVANCES
335    LOGD("Advances - start=%d, count=%d, contextCount=%d, totalAdvance=%f", start, count,
336            contextCount, mTotalAdvance);
337#endif
338}
339
340size_t TextLayoutCacheValue::getSize() const {
341    return sizeof(TextLayoutCacheValue) + sizeof(jfloat) * mAdvances.capacity() +
342            sizeof(jchar) * mGlyphs.capacity();
343}
344
345void TextLayoutCacheValue::initShaperItem(HB_ShaperItem& shaperItem, HB_FontRec* font,
346        FontData* fontData, SkPaint* paint, const UChar* chars, size_t count) {
347    font->klass = &harfbuzzSkiaClass;
348    font->userData = 0;
349
350    // The values which harfbuzzSkiaClass returns are already scaled to
351    // pixel units, so we just set all these to one to disable further
352    // scaling.
353    font->x_ppem = 1;
354    font->y_ppem = 1;
355    font->x_scale = 1;
356    font->y_scale = 1;
357
358    // Reset kerning
359    shaperItem.kerning_applied = false;
360
361    // Define font data
362    fontData->textSize = paint->getTextSize();
363    fontData->textSkewX = paint->getTextSkewX();
364    fontData->textScaleX = paint->getTextScaleX();
365    fontData->flags = paint->getFlags();
366    fontData->hinting = paint->getHinting();
367
368    shaperItem.font = font;
369    shaperItem.font->userData = fontData;
370
371    // We cannot know, ahead of time, how many glyphs a given script run
372    // will produce. We take a guess that script runs will not produce more
373    // than twice as many glyphs as there are code points plus a bit of
374    // padding and fallback if we find that we are wrong.
375    createGlyphArrays(shaperItem, (count + 2) * 2);
376
377    // Create log clusters array
378    shaperItem.log_clusters = new unsigned short[count];
379
380    // Set the string properties
381    shaperItem.string = chars;
382    shaperItem.stringLength = count;
383}
384
385void TextLayoutCacheValue::freeShaperItem(HB_ShaperItem& shaperItem) {
386    deleteGlyphArrays(shaperItem);
387    delete[] shaperItem.log_clusters;
388    HB_FreeFace(shaperItem.face);
389}
390
391unsigned TextLayoutCacheValue::shapeFontRun(HB_ShaperItem& shaperItem, SkPaint* paint,
392        size_t count, bool isRTL) {
393    // Update Harfbuzz Shaper
394    shaperItem.item.pos = 0;
395    shaperItem.item.length = count;
396    shaperItem.item.bidiLevel = isRTL;
397
398    // Get the glyphs base count for offsetting the glyphIDs returned by Harfbuzz
399    // This is needed as the Typeface used for shaping can be not the default one
400    // when we are shapping any script that needs to use a fallback Font.
401    // If we are a "common" script we dont need to shift
402    unsigned result = 0;
403    switch(shaperItem.item.script) {
404        case HB_Script_Arabic:
405        case HB_Script_Hebrew: {
406            const uint16_t* text16 = (const uint16_t*)shaperItem.string;
407            SkUnichar firstUnichar = SkUTF16_NextUnichar(&text16);
408            result = paint->getBaseGlyphCount(firstUnichar);
409            break;
410        }
411        default:
412            break;
413    }
414
415    // Set the correct Typeface depending on the script
416    FontData* data = reinterpret_cast<FontData*>(shaperItem.font->userData);
417    switch(shaperItem.item.script) {
418        case HB_Script_Arabic:
419            data->typeFace = getCachedTypeface(&gArabicTypeface, TYPEFACE_ARABIC);
420#if DEBUG_GLYPHS
421            LOGD("Using Arabic Typeface");
422#endif
423            break;
424
425        case HB_Script_Hebrew:
426            if(paint->getTypeface()) {
427                switch(paint->getTypeface()->style()) {
428                    case SkTypeface::kNormal:
429                    case SkTypeface::kItalic:
430                    default:
431                        data->typeFace = getCachedTypeface(&gHebrewRegularTypeface, TYPE_FACE_HEBREW_REGULAR);
432#if DEBUG_GLYPHS
433                        LOGD("Using Hebrew Regular/Italic Typeface");
434#endif
435                        break;
436                    case SkTypeface::kBold:
437                    case SkTypeface::kBoldItalic:
438                        data->typeFace = getCachedTypeface(&gHebrewBoldTypeface, TYPE_FACE_HEBREW_BOLD);
439#if DEBUG_GLYPHS
440                        LOGD("Using Hebrew Bold/BoldItalic Typeface");
441#endif
442                        break;
443                }
444            } else {
445                data->typeFace = getCachedTypeface(&gHebrewRegularTypeface, TYPE_FACE_HEBREW_REGULAR);
446#if DEBUG_GLYPHS
447                        LOGD("Using Hebrew Regular Typeface");
448#endif
449            }
450            break;
451
452        default:
453            if(paint->getTypeface()) {
454                data->typeFace = paint->getTypeface();
455#if DEBUG_GLYPHS
456            LOGD("Using Paint Typeface");
457#endif
458            } else {
459                data->typeFace = gDefaultTypeface;
460#if DEBUG_GLYPHS
461            LOGD("Using Default Typeface");
462#endif
463            }
464            break;
465    }
466
467    shaperItem.face = HB_NewFace(data, harfbuzzSkiaGetTable);
468
469#if DEBUG_GLYPHS
470    LOGD("Run typeFace = %p", data->typeFace);
471    LOGD("Run typeFace->uniqueID = %d", data->typeFace->uniqueID());
472#endif
473
474    // Shape
475    while (!HB_ShapeItem(&shaperItem)) {
476        // We overflowed our arrays. Resize and retry.
477        // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size.
478        deleteGlyphArrays(shaperItem);
479        createGlyphArrays(shaperItem, shaperItem.num_glyphs << 1);
480    }
481
482    return result;
483}
484
485SkTypeface* TextLayoutCacheValue::getCachedTypeface(SkTypeface** typeface, const char path[]) {
486    if (!*typeface) {
487        *typeface = SkTypeface::CreateFromFile(path);
488#if DEBUG_GLYPHS
489        LOGD("Created SkTypeface from file: %s", path);
490#endif
491    }
492    return *typeface;
493}
494
495void TextLayoutCacheValue::computeValuesWithHarfbuzz(SkPaint* paint, const UChar* chars,
496        size_t start, size_t count, size_t contextCount, int dirFlags,
497        Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
498        Vector<jchar>* const outGlyphs) {
499
500        UBiDiLevel bidiReq = 0;
501        bool forceLTR = false;
502        bool forceRTL = false;
503
504        switch (dirFlags) {
505            case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
506            case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
507            case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
508            case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
509            case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR
510            case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL
511        }
512
513        HB_ShaperItem shaperItem;
514        HB_FontRec font;
515        FontData fontData;
516
517        // Initialize Harfbuzz Shaper
518        initShaperItem(shaperItem, &font, &fontData, paint, chars, contextCount);
519
520        bool useSingleRun = false;
521        bool isRTL = forceRTL;
522        if (forceLTR || forceRTL) {
523            useSingleRun = true;
524        } else {
525            UBiDi* bidi = ubidi_open();
526            if (bidi) {
527                UErrorCode status = U_ZERO_ERROR;
528#if DEBUG_GLYPHS
529                LOGD("computeValuesWithHarfbuzz -- bidiReq=%d", bidiReq);
530#endif
531                ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
532                if (U_SUCCESS(status)) {
533                    int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
534                    ssize_t rc = ubidi_countRuns(bidi, &status);
535#if DEBUG_GLYPHS
536                    LOGD("computeValuesWithHarfbuzz -- dirFlags=%d run-count=%d paraDir=%d",
537                            dirFlags, rc, paraDir);
538#endif
539                    if (U_SUCCESS(status) && rc == 1) {
540                        // Normal case: one run, status is ok
541                        isRTL = (paraDir == 1);
542                        useSingleRun = true;
543                    } else if (!U_SUCCESS(status) || rc < 1) {
544                        LOGW("computeValuesWithHarfbuzz -- need to force to single run");
545                        isRTL = (paraDir == 1);
546                        useSingleRun = true;
547                    } else {
548                        int32_t end = start + count;
549                        for (size_t i = 0; i < size_t(rc); ++i) {
550                            int32_t startRun = -1;
551                            int32_t lengthRun = -1;
552                            UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
553
554                            if (startRun == -1 || lengthRun == -1) {
555                                // Something went wrong when getting the visual run, need to clear
556                                // already computed data before doing a single run pass
557                                LOGW("computeValuesWithHarfbuzz -- visual run is not valid");
558                                outGlyphs->clear();
559                                outAdvances->clear();
560                                *outTotalAdvance = 0;
561                                isRTL = (paraDir == 1);
562                                useSingleRun = true;
563                                break;
564                            }
565
566                            if (startRun >= end) {
567                                continue;
568                            }
569                            int32_t endRun = startRun + lengthRun;
570                            if (endRun <= int32_t(start)) {
571                                continue;
572                            }
573                            if (startRun < int32_t(start)) {
574                                startRun = int32_t(start);
575                            }
576                            if (endRun > end) {
577                                endRun = end;
578                            }
579
580                            lengthRun = endRun - startRun;
581                            isRTL = (runDir == UBIDI_RTL);
582                            jfloat runTotalAdvance = 0;
583#if DEBUG_GLYPHS
584                            LOGD("computeValuesWithHarfbuzz -- run-start=%d run-len=%d isRTL=%d",
585                                    startRun, lengthRun, isRTL);
586#endif
587                            computeRunValuesWithHarfbuzz(paint, chars + startRun, lengthRun, isRTL,
588                                    outAdvances, &runTotalAdvance, outGlyphs);
589
590                            *outTotalAdvance += runTotalAdvance;
591                        }
592                    }
593                } else {
594                    LOGW("computeValuesWithHarfbuzz -- cannot set Para");
595                    useSingleRun = true;
596                    isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
597                }
598                ubidi_close(bidi);
599            } else {
600                LOGW("computeValuesWithHarfbuzz -- cannot ubidi_open()");
601                useSingleRun = true;
602                isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
603            }
604        }
605
606        // Default single run case
607        if (useSingleRun){
608#if DEBUG_GLYPHS
609            LOGD("computeValuesWithHarfbuzz -- Using a SINGLE Run "
610                    "-- run-start=%d run-len=%d isRTL=%d", start, count, isRTL);
611#endif
612            computeRunValuesWithHarfbuzz(paint, chars + start, count, isRTL,
613                    outAdvances, outTotalAdvance, outGlyphs);
614        }
615
616#if DEBUG_GLYPHS
617        LOGD("computeValuesWithHarfbuzz -- total-glyphs-count=%d", outGlyphs->size());
618#endif
619}
620
621static void logGlyphs(HB_ShaperItem shaperItem) {
622    LOGD("Got glyphs - count=%d", shaperItem.num_glyphs);
623    for (size_t i = 0; i < shaperItem.num_glyphs; i++) {
624        LOGD("      glyph[%d]=%d - offset.x=%f offset.y=%f", i, shaperItem.glyphs[i],
625                HBFixedToFloat(shaperItem.offsets[i].x),
626                HBFixedToFloat(shaperItem.offsets[i].y));
627    }
628}
629
630void TextLayoutCacheValue::computeRunValuesWithHarfbuzz(SkPaint* paint, const UChar* chars,
631        size_t count, bool isRTL,
632        Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
633        Vector<jchar>* const outGlyphs) {
634
635    unsigned glyphBaseCount = 0;
636
637    *outTotalAdvance = 0;
638    jfloat totalAdvance = 0;
639
640    unsigned numCodePoints = 0;
641
642    ssize_t startFontRun = 0;
643    ssize_t endFontRun = 0;
644    ssize_t indexFontRun = isRTL ? count - 1 : 0;
645    size_t countFontRun = 0;
646
647    HB_ShaperItem shaperItem;
648    HB_FontRec font;
649    FontData fontData;
650
651    // Zero the Shaper struct
652    memset(&shaperItem, 0, sizeof(shaperItem));
653
654    // Split the BiDi run into Script runs. Harfbuzz will populate the script into the shaperItem
655    while((isRTL) ?
656            hb_utf16_script_run_prev(&numCodePoints, &shaperItem.item, chars,
657                    count, &indexFontRun):
658            hb_utf16_script_run_next(&numCodePoints, &shaperItem.item, chars,
659                    count, &indexFontRun)) {
660
661        startFontRun = shaperItem.item.pos;
662        countFontRun = shaperItem.item.length;
663        endFontRun = startFontRun + countFontRun;
664
665#if DEBUG_GLYPHS
666        LOGD("Shaped Font Run with");
667        LOGD("         -- isRTL=%d", isRTL);
668        LOGD("         -- HB script=%d", shaperItem.item.script);
669        LOGD("         -- startFontRun=%d", startFontRun);
670        LOGD("         -- endFontRun=%d", endFontRun);
671        LOGD("         -- countFontRun=%d", countFontRun);
672        LOGD("         -- run='%s'", String8(chars + startFontRun, countFontRun).string());
673        LOGD("         -- string='%s'", String8(chars, count).string());
674#endif
675
676        // Initialize Harfbuzz Shaper
677        initShaperItem(shaperItem, &font, &fontData, paint, chars + startFontRun, countFontRun);
678
679        // Shape the Font run and get the base glyph count for offsetting the glyphIDs later on
680        glyphBaseCount = shapeFontRun(shaperItem, paint, countFontRun, isRTL);
681
682#if DEBUG_GLYPHS
683        LOGD("HARFBUZZ -- num_glypth=%d - kerning_applied=%d", shaperItem.num_glyphs,
684                shaperItem.kerning_applied);
685        LOGD("         -- isDevKernText=%d", paint->isDevKernText());
686        LOGD("         -- glyphBaseCount=%d", glyphBaseCount);
687
688        logGlyphs(shaperItem);
689#endif
690        if (isRTL) {
691            endFontRun = startFontRun;
692#if DEBUG_GLYPHS
693            LOGD("         -- updated endFontRun=%d", endFontRun);
694#endif
695        } else {
696            startFontRun = endFontRun;
697#if DEBUG_GLYPHS
698            LOGD("         -- updated startFontRun=%d", startFontRun);
699#endif
700        }
701
702        if (shaperItem.advances == NULL || shaperItem.num_glyphs == 0) {
703#if DEBUG_GLYPHS
704            LOGD("HARFBUZZ -- advances array is empty or num_glypth = 0");
705#endif
706            outAdvances->insertAt(0, outAdvances->size(), countFontRun);
707            continue;
708        }
709
710        // Get Advances and their total
711        jfloat currentAdvance = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[0]]);
712        jfloat totalFontRunAdvance = currentAdvance;
713        outAdvances->add(currentAdvance);
714        for (size_t i = 1; i < countFontRun; i++) {
715            size_t clusterPrevious = shaperItem.log_clusters[i - 1];
716            size_t cluster = shaperItem.log_clusters[i];
717            if (cluster == clusterPrevious) {
718                outAdvances->add(0);
719            } else {
720                currentAdvance = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[i]]);
721                totalFontRunAdvance += currentAdvance;
722                outAdvances->add(currentAdvance);
723            }
724        }
725        totalAdvance += totalFontRunAdvance;
726
727#if DEBUG_ADVANCES
728        for (size_t i = 0; i < countFontRun; i++) {
729            LOGD("hb-adv[%d] = %f - log_clusters = %d - total = %f", i,
730                    (*outAdvances)[i], shaperItem.log_clusters[i], totalFontRunAdvance);
731        }
732#endif
733
734        // Get Glyphs and reverse them in place if RTL
735        if (outGlyphs) {
736            size_t countGlyphs = shaperItem.num_glyphs;
737            for (size_t i = 0; i < countGlyphs; i++) {
738                jchar glyph = glyphBaseCount +
739                        (jchar) shaperItem.glyphs[(!isRTL) ? i : countGlyphs - 1 - i];
740#if DEBUG_GLYPHS
741                LOGD("HARFBUZZ  -- glyph[%d]=%d", i, glyph);
742#endif
743                outGlyphs->add(glyph);
744            }
745        }
746        // Cleaning
747        freeShaperItem(shaperItem);
748    }
749    *outTotalAdvance = totalAdvance;
750}
751
752void TextLayoutCacheValue::deleteGlyphArrays(HB_ShaperItem& shaperItem) {
753    delete[] shaperItem.glyphs;
754    delete[] shaperItem.attributes;
755    delete[] shaperItem.advances;
756    delete[] shaperItem.offsets;
757}
758
759void TextLayoutCacheValue::createGlyphArrays(HB_ShaperItem& shaperItem, int size) {
760#if DEBUG_GLYPHS
761    LOGD("createGlyphArrays  -- size=%d", size);
762#endif
763    shaperItem.glyphs = new HB_Glyph[size];
764    shaperItem.attributes = new HB_GlyphAttributes[size];
765    shaperItem.advances = new HB_Fixed[size];
766    shaperItem.offsets = new HB_FixedPoint[size];
767    shaperItem.num_glyphs = size;
768}
769
770} // namespace android
771