TextLayoutCache.cpp revision 6212a4f2ed2a5921de08b527cd1b25e1d155e56b
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#include <unicode/unistr.h>
23#include <unicode/normlzr.h>
24#include <unicode/uchar.h>
25
26extern "C" {
27  #include "harfbuzz-unicode.h"
28}
29
30namespace android {
31
32//--------------------------------------------------------------------------------------------------
33
34ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutEngine);
35
36//--------------------------------------------------------------------------------------------------
37
38TextLayoutCache::TextLayoutCache(TextLayoutShaper* shaper) :
39        mShaper(shaper),
40        mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutValue> >::kUnlimitedCapacity),
41        mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)),
42        mCacheHitCount(0), mNanosecondsSaved(0) {
43    init();
44}
45
46TextLayoutCache::~TextLayoutCache() {
47    mCache.clear();
48}
49
50void TextLayoutCache::init() {
51    mCache.setOnEntryRemovedListener(this);
52
53    mDebugLevel = readRtlDebugLevel();
54    mDebugEnabled = mDebugLevel & kRtlDebugCaches;
55    ALOGD("Using debug level = %d - Debug Enabled = %d", mDebugLevel, mDebugEnabled);
56
57    mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
58
59    if (mDebugEnabled) {
60        ALOGD("Initialization is done - Start time = %lld", mCacheStartTime);
61    }
62
63    mInitialized = true;
64}
65
66/**
67 *  Callbacks
68 */
69void TextLayoutCache::operator()(TextLayoutCacheKey& text, sp<TextLayoutValue>& desc) {
70    size_t totalSizeToDelete = text.getSize() + desc->getSize();
71    mSize -= totalSizeToDelete;
72    if (mDebugEnabled) {
73        ALOGD("Cache value %p deleted, size = %d", desc.get(), totalSizeToDelete);
74    }
75}
76
77/*
78 * Cache clearing
79 */
80void TextLayoutCache::clear() {
81    mCache.clear();
82}
83
84/*
85 * Caching
86 */
87sp<TextLayoutValue> TextLayoutCache::getValue(const SkPaint* paint,
88            const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) {
89    AutoMutex _l(mLock);
90    nsecs_t startTime = 0;
91    if (mDebugEnabled) {
92        startTime = systemTime(SYSTEM_TIME_MONOTONIC);
93    }
94
95    // Create the key
96    TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags);
97
98    // Get value from cache if possible
99    sp<TextLayoutValue> value = mCache.get(key);
100
101    // Value not found for the key, we need to add a new value in the cache
102    if (value == NULL) {
103        if (mDebugEnabled) {
104            startTime = systemTime(SYSTEM_TIME_MONOTONIC);
105        }
106
107        value = new TextLayoutValue(contextCount);
108
109        // Compute advances and store them
110        mShaper->computeValues(value.get(), paint,
111                reinterpret_cast<const UChar*>(text), start, count,
112                size_t(contextCount), int(dirFlags));
113
114        if (mDebugEnabled) {
115            value->setElapsedTime(systemTime(SYSTEM_TIME_MONOTONIC) - startTime);
116        }
117
118        // Don't bother to add in the cache if the entry is too big
119        size_t size = key.getSize() + value->getSize();
120        if (size <= mMaxSize) {
121            // Cleanup to make some room if needed
122            if (mSize + size > mMaxSize) {
123                if (mDebugEnabled) {
124                    ALOGD("Need to clean some entries for making some room for a new entry");
125                }
126                while (mSize + size > mMaxSize) {
127                    // This will call the callback
128                    bool removedOne = mCache.removeOldest();
129                    LOG_ALWAYS_FATAL_IF(!removedOne, "The cache is non-empty but we "
130                            "failed to remove the oldest entry.  "
131                            "mSize = %u, size = %u, mMaxSize = %u, mCache.size() = %u",
132                            mSize, size, mMaxSize, mCache.size());
133                }
134            }
135
136            // Update current cache size
137            mSize += size;
138
139            // Copy the text when we insert the new entry
140            key.internalTextCopy();
141
142            bool putOne = mCache.put(key, value);
143            LOG_ALWAYS_FATAL_IF(!putOne, "Failed to put an entry into the cache.  "
144                    "This indicates that the cache already has an entry with the "
145                    "same key but it should not since we checked earlier!"
146                    " - start = %d, count = %d, contextCount = %d - Text = '%s'",
147                    start, count, contextCount, String8(text + start, count).string());
148
149            if (mDebugEnabled) {
150                nsecs_t totalTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
151                ALOGD("CACHE MISS: Added entry %p "
152                        "with start = %d, count = %d, contextCount = %d, "
153                        "entry size %d bytes, remaining space %d bytes"
154                        " - Compute time %0.6f ms - Put time %0.6f ms - Text = '%s'",
155                        value.get(), start, count, contextCount, size, mMaxSize - mSize,
156                        value->getElapsedTime() * 0.000001f,
157                        (totalTime - value->getElapsedTime()) * 0.000001f,
158                        String8(text + start, count).string());
159            }
160        } else {
161            if (mDebugEnabled) {
162                ALOGD("CACHE MISS: Calculated but not storing entry because it is too big "
163                        "with start = %d, count = %d, contextCount = %d, "
164                        "entry size %d bytes, remaining space %d bytes"
165                        " - Compute time %0.6f ms - Text = '%s'",
166                        start, count, contextCount, size, mMaxSize - mSize,
167                        value->getElapsedTime() * 0.000001f,
168                        String8(text + start, count).string());
169            }
170        }
171    } else {
172        // This is a cache hit, just log timestamp and user infos
173        if (mDebugEnabled) {
174            nsecs_t elapsedTimeThruCacheGet = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
175            mNanosecondsSaved += (value->getElapsedTime() - elapsedTimeThruCacheGet);
176            ++mCacheHitCount;
177
178            if (value->getElapsedTime() > 0) {
179                float deltaPercent = 100 * ((value->getElapsedTime() - elapsedTimeThruCacheGet)
180                        / ((float)value->getElapsedTime()));
181                ALOGD("CACHE HIT #%d with start = %d, count = %d, contextCount = %d"
182                        "- Compute time %0.6f ms - "
183                        "Cache get time %0.6f ms - Gain in percent: %2.2f - Text = '%s'",
184                        mCacheHitCount, start, count, contextCount,
185                        value->getElapsedTime() * 0.000001f,
186                        elapsedTimeThruCacheGet * 0.000001f,
187                        deltaPercent,
188                        String8(text + start, count).string());
189            }
190            if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) {
191                dumpCacheStats();
192            }
193        }
194    }
195    return value;
196}
197
198void TextLayoutCache::dumpCacheStats() {
199    float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize));
200    float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000;
201
202    size_t bytes = 0;
203    size_t cacheSize = mCache.size();
204    for (size_t i = 0; i < cacheSize; i++) {
205        bytes += mCache.getKeyAt(i).getSize() + mCache.getValueAt(i)->getSize();
206    }
207
208    ALOGD("------------------------------------------------");
209    ALOGD("Cache stats");
210    ALOGD("------------------------------------------------");
211    ALOGD("pid       : %d", getpid());
212    ALOGD("running   : %.0f seconds", timeRunningInSec);
213    ALOGD("entries   : %d", cacheSize);
214    ALOGD("max size  : %d bytes", mMaxSize);
215    ALOGD("used      : %d bytes according to mSize, %d bytes actual", mSize, bytes);
216    ALOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent);
217    ALOGD("hits      : %d", mCacheHitCount);
218    ALOGD("saved     : %0.6f ms", mNanosecondsSaved * 0.000001f);
219    ALOGD("------------------------------------------------");
220}
221
222/**
223 * TextLayoutCacheKey
224 */
225TextLayoutCacheKey::TextLayoutCacheKey(): text(NULL), start(0), count(0), contextCount(0),
226        dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0),
227        hinting(SkPaint::kNo_Hinting)  {
228}
229
230TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text,
231        size_t start, size_t count, size_t contextCount, int dirFlags) :
232            text(text), start(start), count(count), contextCount(contextCount),
233            dirFlags(dirFlags) {
234    typeface = paint->getTypeface();
235    textSize = paint->getTextSize();
236    textSkewX = paint->getTextSkewX();
237    textScaleX = paint->getTextScaleX();
238    flags = paint->getFlags();
239    hinting = paint->getHinting();
240}
241
242TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) :
243        text(NULL),
244        textCopy(other.textCopy),
245        start(other.start),
246        count(other.count),
247        contextCount(other.contextCount),
248        dirFlags(other.dirFlags),
249        typeface(other.typeface),
250        textSize(other.textSize),
251        textSkewX(other.textSkewX),
252        textScaleX(other.textScaleX),
253        flags(other.flags),
254        hinting(other.hinting) {
255    if (other.text) {
256        textCopy.setTo(other.text, other.contextCount);
257    }
258}
259
260int TextLayoutCacheKey::compare(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) {
261    int deltaInt = lhs.start - rhs.start;
262    if (deltaInt != 0) return (deltaInt);
263
264    deltaInt = lhs.count - rhs.count;
265    if (deltaInt != 0) return (deltaInt);
266
267    deltaInt = lhs.contextCount - rhs.contextCount;
268    if (deltaInt != 0) return (deltaInt);
269
270    if (lhs.typeface < rhs.typeface) return -1;
271    if (lhs.typeface > rhs.typeface) return +1;
272
273    if (lhs.textSize < rhs.textSize) return -1;
274    if (lhs.textSize > rhs.textSize) return +1;
275
276    if (lhs.textSkewX < rhs.textSkewX) return -1;
277    if (lhs.textSkewX > rhs.textSkewX) return +1;
278
279    if (lhs.textScaleX < rhs.textScaleX) return -1;
280    if (lhs.textScaleX > rhs.textScaleX) return +1;
281
282    deltaInt = lhs.flags - rhs.flags;
283    if (deltaInt != 0) return (deltaInt);
284
285    deltaInt = lhs.hinting - rhs.hinting;
286    if (deltaInt != 0) return (deltaInt);
287
288    deltaInt = lhs.dirFlags - rhs.dirFlags;
289    if (deltaInt) return (deltaInt);
290
291    return memcmp(lhs.getText(), rhs.getText(), lhs.contextCount * sizeof(UChar));
292}
293
294void TextLayoutCacheKey::internalTextCopy() {
295    textCopy.setTo(text, contextCount);
296    text = NULL;
297}
298
299size_t TextLayoutCacheKey::getSize() const {
300    return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount;
301}
302
303/**
304 * TextLayoutCacheValue
305 */
306TextLayoutValue::TextLayoutValue(size_t contextCount) :
307        mTotalAdvance(0), mElapsedTime(0) {
308    // Give a hint for advances and glyphs vectors size
309    mAdvances.setCapacity(contextCount);
310    mGlyphs.setCapacity(contextCount);
311}
312
313size_t TextLayoutValue::getSize() const {
314    return sizeof(TextLayoutValue) + sizeof(jfloat) * mAdvances.capacity() +
315            sizeof(jchar) * mGlyphs.capacity();
316}
317
318void TextLayoutValue::setElapsedTime(uint32_t time) {
319    mElapsedTime = time;
320}
321
322uint32_t TextLayoutValue::getElapsedTime() {
323    return mElapsedTime;
324}
325
326TextLayoutShaper::TextLayoutShaper() : mShaperItemGlyphArraySize(0) {
327    init();
328
329    mFontRec.klass = &harfbuzzSkiaClass;
330    mFontRec.userData = 0;
331
332    // The values which harfbuzzSkiaClass returns are already scaled to
333    // pixel units, so we just set all these to one to disable further
334    // scaling.
335    mFontRec.x_ppem = 1;
336    mFontRec.y_ppem = 1;
337    mFontRec.x_scale = 1;
338    mFontRec.y_scale = 1;
339
340    memset(&mShaperItem, 0, sizeof(mShaperItem));
341
342    mShaperItem.font = &mFontRec;
343    mShaperItem.font->userData = &mShapingPaint;
344}
345
346void TextLayoutShaper::init() {
347    mDefaultTypeface = SkFontHost::CreateTypeface(NULL, NULL, NULL, 0, SkTypeface::kNormal);
348    mArabicTypeface = NULL;
349    mHebrewRegularTypeface = NULL;
350    mHebrewBoldTypeface = NULL;
351    mBengaliTypeface = NULL;
352    mThaiTypeface = NULL;
353    mDevanagariRegularTypeface = NULL;
354    mTamilRegularTypeface = NULL;
355    mTamilBoldTypeface = NULL;
356}
357
358void TextLayoutShaper::unrefTypefaces() {
359    SkSafeUnref(mDefaultTypeface);
360    SkSafeUnref(mArabicTypeface);
361    SkSafeUnref(mHebrewRegularTypeface);
362    SkSafeUnref(mHebrewBoldTypeface);
363    SkSafeUnref(mBengaliTypeface);
364    SkSafeUnref(mThaiTypeface);
365    SkSafeUnref(mDevanagariRegularTypeface);
366    SkSafeUnref(mTamilRegularTypeface);
367    SkSafeUnref(mTamilBoldTypeface);
368}
369
370TextLayoutShaper::~TextLayoutShaper() {
371    unrefTypefaces();
372    deleteShaperItemGlyphArrays();
373}
374
375void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* paint, const UChar* chars,
376        size_t start, size_t count, size_t contextCount, int dirFlags) {
377
378    computeValues(paint, chars, start, count, contextCount, dirFlags,
379            &value->mAdvances, &value->mTotalAdvance, &value->mGlyphs);
380#if DEBUG_ADVANCES
381    ALOGD("Advances - start = %d, count = %d, contextCount = %d, totalAdvance = %f", start, count,
382            contextCount, value->mTotalAdvance);
383#endif
384}
385
386void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars,
387        size_t start, size_t count, size_t contextCount, int dirFlags,
388        Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
389        Vector<jchar>* const outGlyphs) {
390        if (!count) {
391            *outTotalAdvance = 0;
392            return;
393        }
394
395        UBiDiLevel bidiReq = 0;
396        bool forceLTR = false;
397        bool forceRTL = false;
398
399        switch (dirFlags) {
400            case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
401            case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
402            case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
403            case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
404            case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR
405            case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL
406        }
407
408        bool useSingleRun = false;
409        bool isRTL = forceRTL;
410        if (forceLTR || forceRTL) {
411            useSingleRun = true;
412        } else {
413            UBiDi* bidi = ubidi_open();
414            if (bidi) {
415                UErrorCode status = U_ZERO_ERROR;
416#if DEBUG_GLYPHS
417                ALOGD("******** ComputeValues -- start");
418                ALOGD("      -- string = '%s'", String8(chars + start, count).string());
419                ALOGD("      -- start = %d", start);
420                ALOGD("      -- count = %d", count);
421                ALOGD("      -- contextCount = %d", contextCount);
422                ALOGD("      -- bidiReq = %d", bidiReq);
423#endif
424                ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
425                if (U_SUCCESS(status)) {
426                    int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
427                    ssize_t rc = ubidi_countRuns(bidi, &status);
428#if DEBUG_GLYPHS
429                    ALOGD("      -- dirFlags = %d", dirFlags);
430                    ALOGD("      -- paraDir = %d", paraDir);
431                    ALOGD("      -- run-count = %d", int(rc));
432#endif
433                    if (U_SUCCESS(status) && rc == 1) {
434                        // Normal case: one run, status is ok
435                        isRTL = (paraDir == 1);
436                        useSingleRun = true;
437                    } else if (!U_SUCCESS(status) || rc < 1) {
438                        ALOGW("Need to force to single run -- string = '%s',"
439                                " status = %d, rc = %d",
440                                String8(chars + start, count).string(), status, int(rc));
441                        isRTL = (paraDir == 1);
442                        useSingleRun = true;
443                    } else {
444                        int32_t end = start + count;
445                        for (size_t i = 0; i < size_t(rc); ++i) {
446                            int32_t startRun = -1;
447                            int32_t lengthRun = -1;
448                            UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
449
450                            if (startRun == -1 || lengthRun == -1) {
451                                // Something went wrong when getting the visual run, need to clear
452                                // already computed data before doing a single run pass
453                                ALOGW("Visual run is not valid");
454                                outGlyphs->clear();
455                                outAdvances->clear();
456                                *outTotalAdvance = 0;
457                                isRTL = (paraDir == 1);
458                                useSingleRun = true;
459                                break;
460                            }
461
462                            if (startRun >= end) {
463                                continue;
464                            }
465                            int32_t endRun = startRun + lengthRun;
466                            if (endRun <= int32_t(start)) {
467                                continue;
468                            }
469                            if (startRun < int32_t(start)) {
470                                startRun = int32_t(start);
471                            }
472                            if (endRun > end) {
473                                endRun = end;
474                            }
475
476                            lengthRun = endRun - startRun;
477                            isRTL = (runDir == UBIDI_RTL);
478                            jfloat runTotalAdvance = 0;
479#if DEBUG_GLYPHS
480                            ALOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d",
481                                    i, startRun, lengthRun, isRTL);
482#endif
483                            computeRunValues(paint, chars + startRun, lengthRun, isRTL,
484                                    outAdvances, &runTotalAdvance, outGlyphs);
485
486                            *outTotalAdvance += runTotalAdvance;
487                        }
488                    }
489                } else {
490                    ALOGW("Cannot set Para");
491                    useSingleRun = true;
492                    isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
493                }
494                ubidi_close(bidi);
495            } else {
496                ALOGW("Cannot ubidi_open()");
497                useSingleRun = true;
498                isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
499            }
500        }
501
502        // Default single run case
503        if (useSingleRun){
504#if DEBUG_GLYPHS
505            ALOGD("Using a SINGLE BiDi Run "
506                    "-- run-start = %d, run-len = %d, isRTL = %d", start, count, isRTL);
507#endif
508            computeRunValues(paint, chars + start, count, isRTL,
509                    outAdvances, outTotalAdvance, outGlyphs);
510        }
511
512#if DEBUG_GLYPHS
513        ALOGD("      -- Total returned glyphs-count = %d", outGlyphs->size());
514        ALOGD("******** ComputeValues -- end");
515#endif
516}
517
518static void logGlyphs(HB_ShaperItem shaperItem) {
519    ALOGD("         -- glyphs count=%d", shaperItem.num_glyphs);
520    for (size_t i = 0; i < shaperItem.num_glyphs; i++) {
521        ALOGD("         -- glyph[%d] = %d, offset.x = %0.2f, offset.y = %0.2f", i,
522                shaperItem.glyphs[i],
523                HBFixedToFloat(shaperItem.offsets[i].x),
524                HBFixedToFloat(shaperItem.offsets[i].y));
525    }
526}
527
528void TextLayoutShaper::computeRunValues(const SkPaint* paint, const UChar* chars,
529        size_t count, bool isRTL,
530        Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
531        Vector<jchar>* const outGlyphs) {
532    if (!count) {
533        // We cannot shape an empty run.
534        *outTotalAdvance = 0;
535        return;
536    }
537
538    // To be filled in later
539    for (size_t i = 0; i < count; i++) {
540        outAdvances->add(0);
541    }
542    UErrorCode error = U_ZERO_ERROR;
543    bool useNormalizedString = false;
544    for (ssize_t i = count - 1; i >= 0; --i) {
545        UChar ch1 = chars[i];
546        if (::ublock_getCode(ch1) == UBLOCK_COMBINING_DIACRITICAL_MARKS) {
547            // So we have found a diacritic, let's get now the main code point which is paired
548            // with it. As we can have several diacritics in a row, we need to iterate back again
549#if DEBUG_GLYPHS
550            ALOGD("The BiDi run '%s' is containing a Diacritic at position %d",
551                    String8(chars, count).string(), int(i));
552#endif
553            ssize_t j = i - 1;
554            for (; j >= 0;  --j) {
555                UChar ch2 = chars[j];
556                if (::ublock_getCode(ch2) != UBLOCK_COMBINING_DIACRITICAL_MARKS) {
557                    break;
558                }
559            }
560
561            // We could not found the main code point, so we will just use the initial chars
562            if (j < 0) {
563                break;
564            }
565
566#if DEBUG_GLYPHS
567            ALOGD("Found main code point at index %d", int(j));
568#endif
569            // We found the main code point, so we can normalize the "chunk" and fill
570            // the remaining with ZWSP so that the Paint.getTextWidth() APIs will still be able
571            // to get one advance per char
572            mBuffer.remove();
573            Normalizer::normalize(UnicodeString(chars + j, i - j + 1),
574                    UNORM_NFC, 0 /* no options */, mBuffer, error);
575            if (U_SUCCESS(error)) {
576                if (!useNormalizedString) {
577                    useNormalizedString = true;
578                    mNormalizedString.setTo(false /* not terminated*/, chars, count);
579                }
580                // Set the normalized chars
581                for (ssize_t k = j; k < j + mBuffer.length(); ++k) {
582                    mNormalizedString.setCharAt(k, mBuffer.charAt(k - j));
583                }
584                // Fill the remain part with ZWSP (ZWNJ and ZWJ would lead to weird results
585                // because some fonts are missing those glyphs)
586                for (ssize_t k = j + mBuffer.length(); k <= i; ++k) {
587                    mNormalizedString.setCharAt(k, UNICODE_ZWSP);
588                }
589            }
590            i = j - 1;
591        }
592    }
593
594    // Reverse "BiDi mirrored chars" in RTL mode only
595    // See: http://www.unicode.org/Public/6.0.0/ucd/extracted/DerivedBinaryProperties.txt
596    // This is a workaround because Harfbuzz is not able to do mirroring in all cases and
597    // script-run splitting with Harfbuzz is splitting on parenthesis
598    if (isRTL) {
599        for (ssize_t i = 0; i < ssize_t(count); i++) {
600            UChar32 ch = chars[i];
601            if (!u_isMirrored(ch)) continue;
602            if (!useNormalizedString) {
603                useNormalizedString = true;
604                mNormalizedString.setTo(false /* not terminated*/, chars, count);
605            }
606            UChar result =  (UChar) u_charMirror(ch);
607            mNormalizedString.setCharAt(i, result);
608#if DEBUG_GLYPHS
609            ALOGD("Rewriting codepoint '%d' to '%d' at position %d",
610                    ch, mNormalizedString[i], int(i));
611#endif
612        }
613    }
614
615#if DEBUG_GLYPHS
616    if (useNormalizedString) {
617        ALOGD("Will use normalized string '%s', length = %d",
618                    String8(mNormalizedString.getTerminatedBuffer(),
619                            mNormalizedString.length()).string(),
620                    mNormalizedString.length());
621    } else {
622        ALOGD("Normalization is not needed or cannot be done, using initial string");
623    }
624#endif
625
626    assert(mNormalizedString.length() == count);
627
628    // Set the string properties
629    mShaperItem.string = useNormalizedString ? mNormalizedString.getTerminatedBuffer() : chars;
630    mShaperItem.stringLength = count;
631
632    // Define shaping paint properties
633    mShapingPaint.setTextSize(paint->getTextSize());
634    mShapingPaint.setTextSkewX(paint->getTextSkewX());
635    mShapingPaint.setTextScaleX(paint->getTextScaleX());
636    mShapingPaint.setFlags(paint->getFlags());
637    mShapingPaint.setHinting(paint->getHinting());
638
639    // Split the BiDi run into Script runs. Harfbuzz will populate the pos, length and script
640    // into the shaperItem
641    ssize_t indexFontRun = isRTL ? mShaperItem.stringLength - 1 : 0;
642    unsigned numCodePoints = 0;
643    jfloat totalAdvance = 0;
644    while ((isRTL) ?
645            hb_utf16_script_run_prev(&numCodePoints, &mShaperItem.item, mShaperItem.string,
646                    mShaperItem.stringLength, &indexFontRun):
647            hb_utf16_script_run_next(&numCodePoints, &mShaperItem.item, mShaperItem.string,
648                    mShaperItem.stringLength, &indexFontRun)) {
649
650        ssize_t startScriptRun = mShaperItem.item.pos;
651        size_t countScriptRun = mShaperItem.item.length;
652        ssize_t endScriptRun = startScriptRun + countScriptRun;
653
654#if DEBUG_GLYPHS
655        ALOGD("-------- Start of Script Run --------");
656        ALOGD("Shaping Script Run with");
657        ALOGD("         -- isRTL = %d", isRTL);
658        ALOGD("         -- HB script = %d", mShaperItem.item.script);
659        ALOGD("         -- startFontRun = %d", int(startScriptRun));
660        ALOGD("         -- endFontRun = %d", int(endScriptRun));
661        ALOGD("         -- countFontRun = %d", countScriptRun);
662        ALOGD("         -- run = '%s'", String8(chars + startScriptRun, countScriptRun).string());
663        ALOGD("         -- string = '%s'", String8(chars, count).string());
664#endif
665
666        // Initialize Harfbuzz Shaper and get the base glyph count for offsetting the glyphIDs
667        // and shape the Font run
668        size_t glyphBaseCount = shapeFontRun(paint, isRTL);
669
670#if DEBUG_GLYPHS
671        ALOGD("Got from Harfbuzz");
672        ALOGD("         -- glyphBaseCount = %d", glyphBaseCount);
673        ALOGD("         -- num_glypth = %d", mShaperItem.num_glyphs);
674        ALOGD("         -- kerning_applied = %d", mShaperItem.kerning_applied);
675        ALOGD("         -- isDevKernText = %d", paint->isDevKernText());
676
677        logGlyphs(mShaperItem);
678#endif
679
680        if (mShaperItem.advances == NULL || mShaperItem.num_glyphs == 0) {
681#if DEBUG_GLYPHS
682            ALOGD("Advances array is empty or num_glypth = 0");
683#endif
684            continue;
685        }
686
687#if DEBUG_GLYPHS
688        ALOGD("Returned logclusters");
689        for (size_t i = 0; i < mShaperItem.num_glyphs; i++) {
690            ALOGD("         -- lc[%d] = %d, hb-adv[%d] = %0.2f", i, mShaperItem.log_clusters[i],
691                    i, HBFixedToFloat(mShaperItem.advances[i]));
692        }
693#endif
694        // Get Advances and their total
695        jfloat currentAdvance = HBFixedToFloat(mShaperItem.advances[mShaperItem.log_clusters[0]]);
696        jfloat totalFontRunAdvance = currentAdvance;
697        outAdvances->replaceAt(currentAdvance, startScriptRun);
698        for (size_t i = 1; i < countScriptRun; i++) {
699            size_t clusterPrevious = mShaperItem.log_clusters[i - 1];
700            size_t cluster = mShaperItem.log_clusters[i];
701            if (cluster != clusterPrevious) {
702                currentAdvance = HBFixedToFloat(mShaperItem.advances[mShaperItem.log_clusters[i]]);
703                outAdvances->replaceAt(currentAdvance, startScriptRun + i);
704            }
705        }
706        // TODO: can be removed and go back in the previous loop when Harfbuzz log clusters are fixed
707        for (size_t i = 1; i < mShaperItem.num_glyphs; i++) {
708            currentAdvance = HBFixedToFloat(mShaperItem.advances[i]);
709            totalFontRunAdvance += currentAdvance;
710        }
711        totalAdvance += totalFontRunAdvance;
712
713#if DEBUG_ADVANCES
714        ALOGD("Returned advances");
715        for (size_t i = 0; i < countScriptRun; i++) {
716            ALOGD("         -- hb-adv[%d] = %0.2f, log_clusters = %d, total = %0.2f", i,
717                    (*outAdvances)[i], mShaperItem.log_clusters[i], totalFontRunAdvance);
718        }
719#endif
720
721        // Get Glyphs and reverse them in place if RTL
722        if (outGlyphs) {
723            size_t countGlyphs = mShaperItem.num_glyphs;
724#if DEBUG_GLYPHS
725            ALOGD("Returned script run glyphs -- count = %d", countGlyphs);
726#endif
727            for (size_t i = 0; i < countGlyphs; i++) {
728                jchar glyph = glyphBaseCount +
729                        (jchar) mShaperItem.glyphs[(!isRTL) ? i : countGlyphs - 1 - i];
730#if DEBUG_GLYPHS
731                ALOGD("         -- glyph[%d] = %d", i, glyph);
732#endif
733                outGlyphs->add(glyph);
734            }
735        }
736    }
737
738    *outTotalAdvance = totalAdvance;
739
740#if DEBUG_GLYPHS
741    ALOGD("-------- End of Script Run --------");
742#endif
743}
744
745/**
746 * Return the first typeface in the logical change, starting with this typeface,
747 * that contains the specified unichar, or NULL if none is found.
748 *
749 * Note that this function does _not_ increment the reference count on the typeface, as the
750 * assumption is that its lifetime is managed elsewhere - in particular, the fallback typefaces
751 * for the default font live in a global cache.
752 */
753SkTypeface* TextLayoutShaper::typefaceForUnichar(const SkPaint* paint, SkTypeface* typeface,
754        SkUnichar unichar, HB_Script script) {
755    // Set the correct Typeface depending on the script
756    switch (script) {
757    case HB_Script_Arabic:
758        typeface = getCachedTypeface(&mArabicTypeface, HB_Script_Arabic,
759                SkTypeface::kNormal);
760#if DEBUG_GLYPHS
761        ALOGD("Using Arabic Typeface");
762#endif
763        break;
764
765    case HB_Script_Hebrew:
766        if (typeface) {
767            switch (typeface->style()) {
768            case SkTypeface::kBold:
769            case SkTypeface::kBoldItalic:
770                typeface = getCachedTypeface(&mHebrewBoldTypeface, HB_Script_Hebrew,
771                        SkTypeface::kBold);
772#if DEBUG_GLYPHS
773                ALOGD("Using Hebrew Bold/BoldItalic Typeface");
774#endif
775                break;
776
777            case SkTypeface::kNormal:
778            case SkTypeface::kItalic:
779            default:
780                typeface = getCachedTypeface(&mHebrewRegularTypeface, HB_Script_Hebrew,
781                        SkTypeface::kNormal);
782#if DEBUG_GLYPHS
783                ALOGD("Using Hebrew Regular/Italic Typeface");
784#endif
785                break;
786            }
787        } else {
788            typeface = getCachedTypeface(&mHebrewRegularTypeface, HB_Script_Hebrew,
789                    SkTypeface::kNormal);
790#if DEBUG_GLYPHS
791            ALOGD("Using Hebrew Regular Typeface");
792#endif
793        }
794        break;
795
796    case HB_Script_Bengali:
797        typeface = getCachedTypeface(&mBengaliTypeface, HB_Script_Bengali,
798                SkTypeface::kNormal);
799#if DEBUG_GLYPHS
800        ALOGD("Using Bengali Typeface");
801#endif
802        break;
803
804    case HB_Script_Thai:
805        typeface = getCachedTypeface(&mThaiTypeface, HB_Script_Thai,
806                SkTypeface::kNormal);
807#if DEBUG_GLYPHS
808        ALOGD("Using Thai Typeface");
809#endif
810        break;
811
812    case HB_Script_Devanagari:
813       typeface = getCachedTypeface(&mDevanagariRegularTypeface, HB_Script_Devanagari,
814               SkTypeface::kNormal);
815#if DEBUG_GLYPHS
816       ALOGD("Using Devanagari Regular Typeface");
817#endif
818        break;
819
820    case HB_Script_Tamil:
821        if (typeface) {
822            switch (typeface->style()) {
823            case SkTypeface::kBold:
824            case SkTypeface::kBoldItalic:
825                typeface = getCachedTypeface(&mTamilBoldTypeface, HB_Script_Tamil,
826                        SkTypeface::kBold);
827#if DEBUG_GLYPHS
828                ALOGD("Using Tamil Bold Typeface");
829#endif
830                break;
831
832            case SkTypeface::kNormal:
833            case SkTypeface::kItalic:
834            default:
835                typeface = getCachedTypeface(&mTamilRegularTypeface, HB_Script_Tamil,
836                        SkTypeface::kNormal);
837#if DEBUG_GLYPHS
838                ALOGD("Using Tamil Regular Typeface");
839#endif
840                break;
841            }
842        } else {
843            typeface = getCachedTypeface(&mTamilRegularTypeface, HB_Script_Tamil,
844                    SkTypeface::kNormal);
845#if DEBUG_GLYPHS
846            ALOGD("Using Tamil Regular Typeface");
847#endif
848        }
849        break;
850
851    default:
852#if DEBUG_GLYPHS
853        if (typeface) {
854            ALOGD("Using Paint Typeface");
855        }
856#endif
857        break;
858    }
859    return typeface;
860}
861
862size_t TextLayoutShaper::shapeFontRun(const SkPaint* paint, bool isRTL) {
863    // Reset kerning
864    mShaperItem.kerning_applied = false;
865
866    // Update Harfbuzz Shaper
867    mShaperItem.item.bidiLevel = isRTL;
868
869    SkTypeface* typeface = paint->getTypeface();
870
871    // Get the glyphs base count for offsetting the glyphIDs returned by Harfbuzz
872    // This is needed as the Typeface used for shaping can be not the default one
873    // when we are shaping any script that needs to use a fallback Font.
874    // If we are a "common" script we dont need to shift
875    size_t baseGlyphCount = 0;
876    SkUnichar firstUnichar = 0;
877    switch (mShaperItem.item.script) {
878    case HB_Script_Arabic:
879    case HB_Script_Hebrew:
880    case HB_Script_Bengali:
881    case HB_Script_Devanagari:
882    case HB_Script_Tamil:
883    case HB_Script_Thai:{
884        const uint16_t* text16 = (const uint16_t*)(mShaperItem.string + mShaperItem.item.pos);
885        const uint16_t* text16End = text16 + mShaperItem.item.length;
886        firstUnichar = SkUTF16_NextUnichar(&text16);
887        while (firstUnichar == ' ' && text16 < text16End) {
888            firstUnichar = SkUTF16_NextUnichar(&text16);
889        }
890        baseGlyphCount = paint->getBaseGlyphCount(firstUnichar);
891        break;
892    }
893    default:
894        break;
895    }
896
897    // We test the baseGlyphCount to see if the typeface supports the requested script
898    if (baseGlyphCount != 0) {
899        typeface = typefaceForUnichar(paint, typeface, firstUnichar, mShaperItem.item.script);
900    }
901
902    if (!typeface) {
903        typeface = mDefaultTypeface;
904#if DEBUG_GLYPHS
905        ALOGD("Using Default Typeface");
906#endif
907    }
908    mShapingPaint.setTypeface(typeface);
909    mShaperItem.face = getCachedHBFace(typeface);
910
911#if DEBUG_GLYPHS
912    ALOGD("Run typeface = %p, uniqueID = %d, hb_face = %p",
913            typeface, typeface->uniqueID(), mShaperItem.face);
914#endif
915
916    // Shape
917    assert(mShaperItem.item.length > 0); // Harfbuzz will overwrite other memory if length is 0.
918    ensureShaperItemGlyphArrays(mShaperItem.item.length * 3 / 2);
919    mShaperItem.num_glyphs = mShaperItemGlyphArraySize;
920    while (!HB_ShapeItem(&mShaperItem)) {
921        // We overflowed our glyph arrays. Resize and retry.
922        // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size.
923        ensureShaperItemGlyphArrays(mShaperItem.num_glyphs * 2);
924        mShaperItem.num_glyphs = mShaperItemGlyphArraySize;
925    }
926    return baseGlyphCount;
927}
928
929void TextLayoutShaper::ensureShaperItemGlyphArrays(size_t size) {
930    if (size > mShaperItemGlyphArraySize) {
931        deleteShaperItemGlyphArrays();
932        createShaperItemGlyphArrays(size);
933    }
934}
935
936void TextLayoutShaper::createShaperItemGlyphArrays(size_t size) {
937#if DEBUG_GLYPHS
938    ALOGD("Creating Glyph Arrays with size = %d", size);
939#endif
940    mShaperItemGlyphArraySize = size;
941
942    // These arrays are all indexed by glyph.
943    mShaperItem.glyphs = new HB_Glyph[size];
944    mShaperItem.attributes = new HB_GlyphAttributes[size];
945    mShaperItem.advances = new HB_Fixed[size];
946    mShaperItem.offsets = new HB_FixedPoint[size];
947
948    // Although the log_clusters array is indexed by character, Harfbuzz expects that
949    // it is big enough to hold one element per glyph.  So we allocate log_clusters along
950    // with the other glyph arrays above.
951    mShaperItem.log_clusters = new unsigned short[size];
952}
953
954void TextLayoutShaper::deleteShaperItemGlyphArrays() {
955    delete[] mShaperItem.glyphs;
956    delete[] mShaperItem.attributes;
957    delete[] mShaperItem.advances;
958    delete[] mShaperItem.offsets;
959    delete[] mShaperItem.log_clusters;
960}
961
962SkTypeface* TextLayoutShaper::getCachedTypeface(SkTypeface** typeface, HB_Script script,
963        SkTypeface::Style style) {
964    if (!*typeface) {
965        *typeface = SkCreateTypefaceForScript(script, style);
966        // CreateFromFile(path) can return NULL if the path is non existing
967        if (!*typeface) {
968#if DEBUG_GLYPHS
969        ALOGD("No font for Harfbuzz script %d, will use default font", script);
970#endif
971            return mDefaultTypeface;
972        }
973        (*typeface)->ref();
974#if DEBUG_GLYPHS
975        ALOGD("Created SkTypeface for Harfbuzz script %d with uniqueID = %d", script, (*typeface)->uniqueID());
976#endif
977    }
978    return *typeface;
979}
980
981HB_Face TextLayoutShaper::getCachedHBFace(SkTypeface* typeface) {
982    SkFontID fontId = typeface->uniqueID();
983    ssize_t index = mCachedHBFaces.indexOfKey(fontId);
984    if (index >= 0) {
985        return mCachedHBFaces.valueAt(index);
986    }
987    HB_Face face = HB_NewFace(typeface, harfbuzzSkiaGetTable);
988    if (face) {
989#if DEBUG_GLYPHS
990        ALOGD("Created HB_NewFace %p from paint typeface = %p", face, typeface);
991#endif
992        mCachedHBFaces.add(fontId, face);
993    }
994    return face;
995}
996
997void TextLayoutShaper::purgeCaches() {
998    size_t cacheSize = mCachedHBFaces.size();
999    for (size_t i = 0; i < cacheSize; i++) {
1000        HB_FreeFace(mCachedHBFaces.valueAt(i));
1001    }
1002    mCachedHBFaces.clear();
1003    unrefTypefaces();
1004    init();
1005}
1006
1007TextLayoutEngine::TextLayoutEngine() {
1008    mShaper = new TextLayoutShaper();
1009#if USE_TEXT_LAYOUT_CACHE
1010    mTextLayoutCache = new TextLayoutCache(mShaper);
1011#else
1012    mTextLayoutCache = NULL;
1013#endif
1014}
1015
1016TextLayoutEngine::~TextLayoutEngine() {
1017    delete mTextLayoutCache;
1018    delete mShaper;
1019}
1020
1021sp<TextLayoutValue> TextLayoutEngine::getValue(const SkPaint* paint, const jchar* text,
1022        jint start, jint count, jint contextCount, jint dirFlags) {
1023    sp<TextLayoutValue> value;
1024#if USE_TEXT_LAYOUT_CACHE
1025    value = mTextLayoutCache->getValue(paint, text, start, count,
1026            contextCount, dirFlags);
1027    if (value == NULL) {
1028        ALOGE("Cannot get TextLayoutCache value for text = '%s'",
1029                String8(text + start, count).string());
1030    }
1031#else
1032    value = new TextLayoutValue(count);
1033    mShaper->computeValues(value.get(), paint,
1034            reinterpret_cast<const UChar*>(text), start, count, contextCount, dirFlags);
1035#endif
1036    return value;
1037}
1038
1039void TextLayoutEngine::purgeCaches() {
1040#if USE_TEXT_LAYOUT_CACHE
1041    mTextLayoutCache->clear();
1042    mShaper->purgeCaches();
1043#if DEBUG_GLYPHS
1044    ALOGD("Purged TextLayoutEngine caches");
1045#endif
1046#endif
1047}
1048
1049
1050} // namespace android
1051