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