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