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