TextLayoutCache.cpp revision e187a2f55fe8684c853a0701cbc4a71392f437e0
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            if (!gArabicTypeface) {
419                gArabicTypeface = SkTypeface::CreateFromFile(TYPEFACE_ARABIC);
420            }
421            data->typeFace = gArabicTypeface;
422#if DEBUG_GLYPHS
423            LOGD("Using Arabic Typeface");
424#endif
425            break;
426
427        case HB_Script_Hebrew:
428            if(paint->getTypeface()) {
429                switch(paint->getTypeface()->style()) {
430                    case SkTypeface::kNormal:
431                    case SkTypeface::kItalic:
432                    default:
433                        if (!gHebrewRegularTypeface) {
434                            gHebrewRegularTypeface = SkTypeface::CreateFromFile(
435                                    TYPE_FACE_HEBREW_REGULAR);
436                        }
437                        data->typeFace = gHebrewRegularTypeface;
438#if DEBUG_GLYPHS
439                        LOGD("Using Hebrew Regular/Italic Typeface");
440#endif
441                        break;
442                    case SkTypeface::kBold:
443                    case SkTypeface::kBoldItalic:
444                        if (!gHebrewBoldTypeface) {
445                            gHebrewBoldTypeface = SkTypeface::CreateFromFile(
446                                    TYPE_FACE_HEBREW_BOLD);
447                        }
448                        data->typeFace = gHebrewBoldTypeface;
449#if DEBUG_GLYPHS
450                        LOGD("Using Hebrew Bold/BoldItalic Typeface");
451#endif
452                        break;
453                }
454            } else {
455                data->typeFace = gHebrewRegularTypeface;
456#if DEBUG_GLYPHS
457                        LOGD("Using Hebrew Regular Typeface");
458#endif
459            }
460            break;
461
462        default:
463            if(paint->getTypeface()) {
464                data->typeFace = paint->getTypeface();
465#if DEBUG_GLYPHS
466            LOGD("Using Paint Typeface");
467#endif
468            } else {
469                data->typeFace = gDefaultTypeface;
470#if DEBUG_GLYPHS
471            LOGD("Using Default Typeface");
472#endif
473            }
474            break;
475    }
476
477    shaperItem.face = HB_NewFace(data, harfbuzzSkiaGetTable);
478
479#if DEBUG_GLYPHS
480    LOGD("Run typeFace = %p", data->typeFace);
481    LOGD("Run typeFace->uniqueID = %d", data->typeFace->uniqueID());
482#endif
483
484    // Shape
485    while (!HB_ShapeItem(&shaperItem)) {
486        // We overflowed our arrays. Resize and retry.
487        // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size.
488        deleteGlyphArrays(shaperItem);
489        createGlyphArrays(shaperItem, shaperItem.num_glyphs << 1);
490    }
491
492    return result;
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