TextLayoutCache.cpp revision aaedde51b76901ff05f2a2348eb41f0f5323d954
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 <utils/JenkinsHash.h>
20
21#include "TextLayoutCache.h"
22#include "TextLayout.h"
23#include "SkFontHost.h"
24#include "SkTypeface_android.h"
25#include "HarfBuzzNGFaceSkia.h"
26#include <unicode/unistr.h>
27#include <unicode/uchar.h>
28#include <hb-icu.h>
29
30namespace android {
31
32//--------------------------------------------------------------------------------------------------
33
34ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutEngine);
35
36//--------------------------------------------------------------------------------------------------
37
38TextLayoutCache::TextLayoutCache(TextLayoutShaper* shaper) :
39        mShaper(shaper),
40        mCache(LruCache<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::purgeCaches() {
81    AutoMutex _l(mLock);
82    mCache.clear();
83    mShaper->purgeCaches();
84}
85
86/*
87 * Caching
88 */
89sp<TextLayoutValue> TextLayoutCache::getValue(const SkPaint* paint,
90            const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) {
91    AutoMutex _l(mLock);
92    nsecs_t startTime = 0;
93    if (mDebugEnabled) {
94        startTime = systemTime(SYSTEM_TIME_MONOTONIC);
95    }
96
97    // Create the key
98    TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags);
99
100    // Get value from cache if possible
101    sp<TextLayoutValue> value = mCache.get(key);
102
103    // Value not found for the key, we need to add a new value in the cache
104    if (value == NULL) {
105        if (mDebugEnabled) {
106            startTime = systemTime(SYSTEM_TIME_MONOTONIC);
107        }
108
109        value = new TextLayoutValue(contextCount);
110
111        // Compute advances and store them
112        mShaper->computeValues(value.get(), paint,
113                reinterpret_cast<const UChar*>(key.getText()), start, count,
114                size_t(contextCount), int(dirFlags));
115
116        if (mDebugEnabled) {
117            value->setElapsedTime(systemTime(SYSTEM_TIME_MONOTONIC) - startTime);
118        }
119
120        // Don't bother to add in the cache if the entry is too big
121        size_t size = key.getSize() + value->getSize();
122        if (size <= mMaxSize) {
123            // Cleanup to make some room if needed
124            if (mSize + size > mMaxSize) {
125                if (mDebugEnabled) {
126                    ALOGD("Need to clean some entries for making some room for a new entry");
127                }
128                while (mSize + size > mMaxSize) {
129                    // This will call the callback
130                    bool removedOne = mCache.removeOldest();
131                    LOG_ALWAYS_FATAL_IF(!removedOne, "The cache is non-empty but we "
132                            "failed to remove the oldest entry.  "
133                            "mSize = %u, size = %u, mMaxSize = %u, mCache.size() = %u",
134                            mSize, size, mMaxSize, mCache.size());
135                }
136            }
137
138            // Update current cache size
139            mSize += size;
140
141            bool putOne = mCache.put(key, value);
142            LOG_ALWAYS_FATAL_IF(!putOne, "Failed to put an entry into the cache.  "
143                    "This indicates that the cache already has an entry with the "
144                    "same key but it should not since we checked earlier!"
145                    " - start = %d, count = %d, contextCount = %d - Text = '%s'",
146                    start, count, contextCount, String8(key.getText() + start, count).string());
147
148            if (mDebugEnabled) {
149                nsecs_t totalTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
150                ALOGD("CACHE MISS: Added entry %p "
151                        "with start = %d, count = %d, contextCount = %d, "
152                        "entry size %d bytes, remaining space %d bytes"
153                        " - Compute time %0.6f ms - Put time %0.6f ms - Text = '%s'",
154                        value.get(), start, count, contextCount, size, mMaxSize - mSize,
155                        value->getElapsedTime() * 0.000001f,
156                        (totalTime - value->getElapsedTime()) * 0.000001f,
157                        String8(key.getText() + start, count).string());
158            }
159        } else {
160            if (mDebugEnabled) {
161                ALOGD("CACHE MISS: Calculated but not storing entry because it is too big "
162                        "with start = %d, count = %d, contextCount = %d, "
163                        "entry size %d bytes, remaining space %d bytes"
164                        " - Compute time %0.6f ms - Text = '%s'",
165                        start, count, contextCount, size, mMaxSize - mSize,
166                        value->getElapsedTime() * 0.000001f,
167                        String8(key.getText() + start, count).string());
168            }
169        }
170    } else {
171        // This is a cache hit, just log timestamp and user infos
172        if (mDebugEnabled) {
173            nsecs_t elapsedTimeThruCacheGet = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
174            mNanosecondsSaved += (value->getElapsedTime() - elapsedTimeThruCacheGet);
175            ++mCacheHitCount;
176
177            if (value->getElapsedTime() > 0) {
178                float deltaPercent = 100 * ((value->getElapsedTime() - elapsedTimeThruCacheGet)
179                        / ((float)value->getElapsedTime()));
180                ALOGD("CACHE HIT #%d with start = %d, count = %d, contextCount = %d"
181                        "- Compute time %0.6f ms - "
182                        "Cache get time %0.6f ms - Gain in percent: %2.2f - Text = '%s'",
183                        mCacheHitCount, start, count, contextCount,
184                        value->getElapsedTime() * 0.000001f,
185                        elapsedTimeThruCacheGet * 0.000001f,
186                        deltaPercent,
187                        String8(key.getText() + start, count).string());
188            }
189            if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) {
190                dumpCacheStats();
191            }
192        }
193    }
194    return value;
195}
196
197void TextLayoutCache::dumpCacheStats() {
198    float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize));
199    float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000;
200
201    size_t cacheSize = mCache.size();
202
203    ALOGD("------------------------------------------------");
204    ALOGD("Cache stats");
205    ALOGD("------------------------------------------------");
206    ALOGD("pid       : %d", getpid());
207    ALOGD("running   : %.0f seconds", timeRunningInSec);
208    ALOGD("entries   : %d", cacheSize);
209    ALOGD("max size  : %d bytes", mMaxSize);
210    ALOGD("used      : %d bytes according to mSize", mSize);
211    ALOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent);
212    ALOGD("hits      : %d", mCacheHitCount);
213    ALOGD("saved     : %0.6f ms", mNanosecondsSaved * 0.000001f);
214    ALOGD("------------------------------------------------");
215}
216
217/**
218 * TextLayoutCacheKey
219 */
220TextLayoutCacheKey::TextLayoutCacheKey(): start(0), count(0), contextCount(0),
221        dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0),
222        hinting(SkPaint::kNo_Hinting), variant(SkPaint::kDefault_Variant), language()  {
223}
224
225TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text,
226        size_t start, size_t count, size_t contextCount, int dirFlags) :
227            start(start), count(count), contextCount(contextCount),
228            dirFlags(dirFlags) {
229    textCopy.setTo(text, contextCount);
230    typeface = paint->getTypeface();
231    textSize = paint->getTextSize();
232    textSkewX = paint->getTextSkewX();
233    textScaleX = paint->getTextScaleX();
234    flags = paint->getFlags();
235    hinting = paint->getHinting();
236    variant = paint->getFontVariant();
237    language = paint->getLanguage();
238}
239
240TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) :
241        textCopy(other.textCopy),
242        start(other.start),
243        count(other.count),
244        contextCount(other.contextCount),
245        dirFlags(other.dirFlags),
246        typeface(other.typeface),
247        textSize(other.textSize),
248        textSkewX(other.textSkewX),
249        textScaleX(other.textScaleX),
250        flags(other.flags),
251        hinting(other.hinting),
252        variant(other.variant),
253        language(other.language) {
254}
255
256int TextLayoutCacheKey::compare(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) {
257    int deltaInt = lhs.start - rhs.start;
258    if (deltaInt != 0) return (deltaInt);
259
260    deltaInt = lhs.count - rhs.count;
261    if (deltaInt != 0) return (deltaInt);
262
263    deltaInt = lhs.contextCount - rhs.contextCount;
264    if (deltaInt != 0) return (deltaInt);
265
266    if (lhs.typeface < rhs.typeface) return -1;
267    if (lhs.typeface > rhs.typeface) return +1;
268
269    if (lhs.textSize < rhs.textSize) return -1;
270    if (lhs.textSize > rhs.textSize) return +1;
271
272    if (lhs.textSkewX < rhs.textSkewX) return -1;
273    if (lhs.textSkewX > rhs.textSkewX) return +1;
274
275    if (lhs.textScaleX < rhs.textScaleX) return -1;
276    if (lhs.textScaleX > rhs.textScaleX) return +1;
277
278    deltaInt = lhs.flags - rhs.flags;
279    if (deltaInt != 0) return (deltaInt);
280
281    deltaInt = lhs.hinting - rhs.hinting;
282    if (deltaInt != 0) return (deltaInt);
283
284    deltaInt = lhs.dirFlags - rhs.dirFlags;
285    if (deltaInt) return (deltaInt);
286
287    deltaInt = lhs.variant - rhs.variant;
288    if (deltaInt) return (deltaInt);
289
290    if (lhs.language < rhs.language) return -1;
291    if (lhs.language > rhs.language) return +1;
292
293    return memcmp(lhs.getText(), rhs.getText(), lhs.contextCount * sizeof(UChar));
294}
295
296size_t TextLayoutCacheKey::getSize() const {
297    return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount;
298}
299
300hash_t TextLayoutCacheKey::hash() const {
301    uint32_t hash = JenkinsHashMix(0, start);
302    hash = JenkinsHashMix(hash, count);
303    /* contextCount not needed because it's included in text, below */
304    hash = JenkinsHashMix(hash, hash_type(typeface));
305    hash = JenkinsHashMix(hash, hash_type(textSize));
306    hash = JenkinsHashMix(hash, hash_type(textSkewX));
307    hash = JenkinsHashMix(hash, hash_type(textScaleX));
308    hash = JenkinsHashMix(hash, flags);
309    hash = JenkinsHashMix(hash, hinting);
310    hash = JenkinsHashMix(hash, variant);
311    // Note: leaving out language is not problematic, as equality comparisons
312    // are still valid - the only bad thing that could happen is collisions.
313    hash = JenkinsHashMixShorts(hash, getText(), contextCount);
314    return JenkinsHashWhiten(hash);
315}
316
317/**
318 * TextLayoutCacheValue
319 */
320TextLayoutValue::TextLayoutValue(size_t contextCount) :
321        mTotalAdvance(0), mElapsedTime(0) {
322    // Give a hint for advances and glyphs vectors size
323    mAdvances.setCapacity(contextCount);
324    mGlyphs.setCapacity(contextCount);
325    mPos.setCapacity(contextCount * 2);
326}
327
328size_t TextLayoutValue::getSize() const {
329    return sizeof(TextLayoutValue) + sizeof(jfloat) * mAdvances.capacity() +
330            sizeof(jchar) * mGlyphs.capacity() + sizeof(jfloat) * mPos.capacity();
331}
332
333void TextLayoutValue::setElapsedTime(uint32_t time) {
334    mElapsedTime = time;
335}
336
337uint32_t TextLayoutValue::getElapsedTime() {
338    return mElapsedTime;
339}
340
341TextLayoutShaper::TextLayoutShaper() {
342    init();
343
344    mBuffer = hb_buffer_create();
345}
346
347void TextLayoutShaper::init() {
348    mDefaultTypeface = SkFontHost::CreateTypeface(NULL, NULL, NULL, 0, SkTypeface::kNormal);
349}
350
351void TextLayoutShaper::unrefTypefaces() {
352    SkSafeUnref(mDefaultTypeface);
353}
354
355TextLayoutShaper::~TextLayoutShaper() {
356    hb_buffer_destroy(mBuffer);
357
358    unrefTypefaces();
359}
360
361void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* paint, const UChar* chars,
362        size_t start, size_t count, size_t contextCount, int dirFlags) {
363
364    computeValues(paint, chars, start, count, contextCount, dirFlags,
365            &value->mAdvances, &value->mTotalAdvance, &value->mGlyphs, &value->mPos);
366#if DEBUG_ADVANCES
367    ALOGD("Advances - start = %d, count = %d, contextCount = %d, totalAdvance = %f", start, count,
368            contextCount, value->mTotalAdvance);
369#endif
370}
371
372void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars,
373        size_t start, size_t count, size_t contextCount, int dirFlags,
374        Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
375        Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) {
376        *outTotalAdvance = 0;
377        if (!count) {
378            return;
379        }
380
381        UBiDiLevel bidiReq = 0;
382        bool forceLTR = false;
383        bool forceRTL = false;
384
385        switch (dirFlags & kBidi_Mask) {
386            case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
387            case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
388            case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
389            case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
390            case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR
391            case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL
392        }
393
394        bool useSingleRun = false;
395        bool isRTL = forceRTL;
396        if (forceLTR || forceRTL) {
397            useSingleRun = true;
398        } else {
399            UBiDi* bidi = ubidi_open();
400            if (bidi) {
401                UErrorCode status = U_ZERO_ERROR;
402#if DEBUG_GLYPHS
403                ALOGD("******** ComputeValues -- start");
404                ALOGD("      -- string = '%s'", String8(chars + start, count).string());
405                ALOGD("      -- start = %d", start);
406                ALOGD("      -- count = %d", count);
407                ALOGD("      -- contextCount = %d", contextCount);
408                ALOGD("      -- bidiReq = %d", bidiReq);
409#endif
410                ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
411                if (U_SUCCESS(status)) {
412                    int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
413                    ssize_t rc = ubidi_countRuns(bidi, &status);
414#if DEBUG_GLYPHS
415                    ALOGD("      -- dirFlags = %d", dirFlags);
416                    ALOGD("      -- paraDir = %d", paraDir);
417                    ALOGD("      -- run-count = %d", int(rc));
418#endif
419                    if (U_SUCCESS(status) && rc == 1) {
420                        // Normal case: one run, status is ok
421                        isRTL = (paraDir == 1);
422                        useSingleRun = true;
423                    } else if (!U_SUCCESS(status) || rc < 1) {
424                        ALOGW("Need to force to single run -- string = '%s',"
425                                " status = %d, rc = %d",
426                                String8(chars + start, count).string(), status, int(rc));
427                        isRTL = (paraDir == 1);
428                        useSingleRun = true;
429                    } else {
430                        int32_t end = start + count;
431                        for (size_t i = 0; i < size_t(rc); ++i) {
432                            int32_t startRun = -1;
433                            int32_t lengthRun = -1;
434                            UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
435
436                            if (startRun == -1 || lengthRun == -1) {
437                                // Something went wrong when getting the visual run, need to clear
438                                // already computed data before doing a single run pass
439                                ALOGW("Visual run is not valid");
440                                outGlyphs->clear();
441                                outAdvances->clear();
442                                outPos->clear();
443                                *outTotalAdvance = 0;
444                                isRTL = (paraDir == 1);
445                                useSingleRun = true;
446                                break;
447                            }
448
449                            if (startRun >= end) {
450                                continue;
451                            }
452                            int32_t endRun = startRun + lengthRun;
453                            if (endRun <= int32_t(start)) {
454                                continue;
455                            }
456                            if (startRun < int32_t(start)) {
457                                startRun = int32_t(start);
458                            }
459                            if (endRun > end) {
460                                endRun = end;
461                            }
462
463                            lengthRun = endRun - startRun;
464                            isRTL = (runDir == UBIDI_RTL);
465#if DEBUG_GLYPHS
466                            ALOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d",
467                                    i, startRun, lengthRun, isRTL);
468#endif
469                            computeRunValues(paint, chars, startRun, lengthRun, contextCount, isRTL,
470                                    outAdvances, outTotalAdvance, outGlyphs, outPos);
471
472                        }
473                    }
474                } else {
475                    ALOGW("Cannot set Para");
476                    useSingleRun = true;
477                    isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
478                }
479                ubidi_close(bidi);
480            } else {
481                ALOGW("Cannot ubidi_open()");
482                useSingleRun = true;
483                isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
484            }
485        }
486
487        // Default single run case
488        if (useSingleRun){
489#if DEBUG_GLYPHS
490            ALOGD("Using a SINGLE BiDi Run "
491                    "-- run-start = %d, run-len = %d, isRTL = %d", start, count, isRTL);
492#endif
493            computeRunValues(paint, chars, start, count, contextCount, isRTL,
494                    outAdvances, outTotalAdvance, outGlyphs, outPos);
495        }
496
497#if DEBUG_GLYPHS
498        ALOGD("      -- Total returned glyphs-count = %d", outGlyphs->size());
499        ALOGD("******** ComputeValues -- end");
500#endif
501}
502
503#define HB_IsHighSurrogate(ucs) \
504    (((ucs) & 0xfc00) == 0xd800)
505
506#define HB_IsLowSurrogate(ucs) \
507    (((ucs) & 0xfc00) == 0xdc00)
508
509#ifndef HB_SurrogateToUcs4
510#define HB_SurrogateToUcs4_(high, low) \
511    (((hb_codepoint_t)(high))<<10) + (low) - 0x35fdc00;
512#endif
513
514#define HB_InvalidCodePoint ~0u
515
516hb_codepoint_t
517utf16_to_code_point(const uint16_t *chars, size_t len, ssize_t *iter) {
518  const uint16_t v = chars[(*iter)++];
519  if (HB_IsHighSurrogate(v)) {
520    // surrogate pair
521    if (size_t(*iter) >= len) {
522      // the surrogate is incomplete.
523      return HB_InvalidCodePoint;
524    }
525    const uint16_t v2 = chars[(*iter)++];
526    if (!HB_IsLowSurrogate(v2)) {
527      // invalidate surrogate pair.
528      (*iter)--;
529      return HB_InvalidCodePoint;
530    }
531
532    return HB_SurrogateToUcs4(v, v2);
533  }
534
535  if (HB_IsLowSurrogate(v)) {
536    // this isn't a valid code point
537    return HB_InvalidCodePoint;
538  }
539
540  return v;
541}
542
543hb_codepoint_t
544utf16_to_code_point_prev(const uint16_t *chars, size_t len, ssize_t *iter) {
545  const uint16_t v = chars[(*iter)--];
546  if (HB_IsLowSurrogate(v)) {
547    // surrogate pair
548    if (*iter < 0) {
549      // the surrogate is incomplete.
550      return HB_InvalidCodePoint;
551    }
552    const uint16_t v2 = chars[(*iter)--];
553    if (!HB_IsHighSurrogate(v2)) {
554      // invalidate surrogate pair.
555      (*iter)++;
556      return HB_InvalidCodePoint;
557    }
558
559    return HB_SurrogateToUcs4(v2, v);
560  }
561
562  if (HB_IsHighSurrogate(v)) {
563    // this isn't a valid code point
564    return HB_InvalidCodePoint;
565  }
566
567  return v;
568}
569
570struct ScriptRun {
571    hb_script_t script;
572    size_t pos;
573    size_t length;
574};
575
576hb_script_t code_point_to_script(hb_codepoint_t codepoint) {
577    static hb_unicode_funcs_t* u;
578    if (!u) {
579        u = hb_icu_get_unicode_funcs();
580    }
581    return hb_unicode_script(u, codepoint);
582}
583
584bool
585hb_utf16_script_run_next(ScriptRun* run, const uint16_t *chars, size_t len, ssize_t *iter) {
586  if (size_t(*iter) == len)
587    return false;
588
589  run->pos = *iter;
590  const uint32_t init_cp = utf16_to_code_point(chars, len, iter);
591  const hb_script_t init_script = code_point_to_script(init_cp);
592  hb_script_t current_script = init_script;
593  run->script = init_script;
594
595  for (;;) {
596    if (size_t(*iter) == len)
597      break;
598    const ssize_t prev_iter = *iter;
599    const uint32_t cp = utf16_to_code_point(chars, len, iter);
600    const hb_script_t script = code_point_to_script(cp);
601
602    if (script != current_script) {
603        /* BEGIN android-changed
604           The condition was not correct by doing "a == b == constant"
605           END android-changed */
606      if (current_script == HB_SCRIPT_INHERITED && init_script == HB_SCRIPT_INHERITED) {
607        // If we started off as inherited, we take whatever we can find.
608        run->script = script;
609        current_script = script;
610        continue;
611      } else if (script == HB_SCRIPT_INHERITED) {
612        continue;
613      } else {
614        *iter = prev_iter;
615        break;
616      }
617    }
618  }
619
620  if (run->script == HB_SCRIPT_INHERITED)
621    run->script = HB_SCRIPT_COMMON;
622
623  run->length = *iter - run->pos;
624  return true;
625}
626
627bool
628hb_utf16_script_run_prev(ScriptRun* run, const uint16_t *chars, size_t len, ssize_t *iter) {
629  if (*iter == -1)
630    return false;
631
632  const size_t ending_index = *iter;
633  const uint32_t init_cp = utf16_to_code_point_prev(chars, len, iter);
634  const hb_script_t init_script = code_point_to_script(init_cp);
635  hb_script_t current_script = init_script;
636  run->script = init_script;
637
638  for (;;) {
639    if (*iter < 0)
640      break;
641    const ssize_t prev_iter = *iter;
642    const uint32_t cp = utf16_to_code_point_prev(chars, len, iter);
643    const hb_script_t script = code_point_to_script(cp);
644
645    if (script != current_script) {
646      if (current_script == HB_SCRIPT_INHERITED && init_script == HB_SCRIPT_INHERITED) {
647        // If we started off as inherited, we take whatever we can find.
648        run->script = script;
649        current_script = script;
650        continue;
651      } else if (script == HB_SCRIPT_INHERITED) {
652        /* BEGIN android-changed
653           We apply the same fix for Chrome to Android.
654           Chrome team will talk with upsteam about it.
655           Just assume that whatever follows this combining character is within
656           the same script.  This is incorrect if you had language1 + combining
657           char + language 2, but that is rare and this code is suspicious
658           anyway.
659           END android-changed */
660        continue;
661      } else {
662        *iter = prev_iter;
663        break;
664      }
665    }
666  }
667
668  if (run->script == HB_SCRIPT_INHERITED)
669    run->script = HB_SCRIPT_COMMON;
670
671  run->pos = *iter + 1;
672  run->length = ending_index - *iter;
673  return true;
674}
675
676
677static void logGlyphs(hb_buffer_t* buffer) {
678    unsigned int numGlyphs;
679    hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer, &numGlyphs);
680    hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(buffer, NULL);
681    ALOGD("         -- glyphs count=%d", numGlyphs);
682    for (size_t i = 0; i < numGlyphs; i++) {
683        ALOGD("         -- glyph[%d] = %d, cluster = %u, advance = %0.2f, offset.x = %0.2f, offset.y = %0.2f", i,
684                info[i].codepoint,
685                info[i].cluster,
686                HBFixedToFloat(positions[i].x_advance),
687                HBFixedToFloat(positions[i].x_offset),
688                HBFixedToFloat(positions[i].y_offset));
689    }
690}
691
692void TextLayoutShaper::computeRunValues(const SkPaint* paint, const UChar* contextChars,
693        size_t start, size_t count, size_t contextCount, bool isRTL,
694        Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
695        Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) {
696    if (!count) {
697        // We cannot shape an empty run.
698        return;
699    }
700
701    // To be filled in later
702    for (size_t i = 0; i < count; i++) {
703        outAdvances->add(0);
704    }
705
706    // Set the string properties
707    const UChar* chars = contextChars + start;
708
709    // Define shaping paint properties
710    mShapingPaint.setTextSize(paint->getTextSize());
711    float skewX = paint->getTextSkewX();
712    mShapingPaint.setTextSkewX(skewX);
713    mShapingPaint.setTextScaleX(paint->getTextScaleX());
714    mShapingPaint.setFlags(paint->getFlags());
715    mShapingPaint.setHinting(paint->getHinting());
716    mShapingPaint.setFontVariant(paint->getFontVariant());
717    mShapingPaint.setLanguage(paint->getLanguage());
718
719    // Split the BiDi run into Script runs. Harfbuzz will populate the pos, length and script
720    // into the shaperItem
721    ssize_t indexFontRun = isRTL ? count - 1 : 0;
722    jfloat totalAdvance = *outTotalAdvance;
723    ScriptRun run;  // relative to chars
724    while ((isRTL) ?
725            hb_utf16_script_run_prev(&run, chars, count, &indexFontRun):
726            hb_utf16_script_run_next(&run, chars, count, &indexFontRun)) {
727
728#if DEBUG_GLYPHS
729        ALOGD("-------- Start of Script Run --------");
730        ALOGD("Shaping Script Run with");
731        ALOGD("         -- isRTL = %d", isRTL);
732        ALOGD("         -- HB script = %c%c%c%c", HB_UNTAG(run.script));
733        ALOGD("         -- run.pos = %d", int(run.pos));
734        ALOGD("         -- run.length = %d", int(run.length));
735        ALOGD("         -- run = '%s'", String8(chars + run.pos, run.length).string());
736        ALOGD("         -- string = '%s'", String8(chars, count).string());
737#endif
738
739        hb_buffer_reset(mBuffer);
740        // Note: if we want to set unicode functions, etc., this is the place.
741
742        hb_buffer_set_direction(mBuffer, isRTL ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
743        hb_buffer_set_script(mBuffer, run.script);
744        // Should set language here (for bug 7004056)
745        hb_buffer_add_utf16(mBuffer, contextChars, contextCount, start + run.pos, run.length);
746
747        // Initialize Harfbuzz Shaper and get the base glyph count for offsetting the glyphIDs
748        // and shape the Font run
749        size_t glyphBaseCount = shapeFontRun(paint);
750        unsigned int numGlyphs;
751        hb_glyph_info_t* info = hb_buffer_get_glyph_infos(mBuffer, &numGlyphs);
752        hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(mBuffer, NULL);
753
754#if DEBUG_GLYPHS
755        ALOGD("Got from Harfbuzz");
756        ALOGD("         -- glyphBaseCount = %d", glyphBaseCount);
757        ALOGD("         -- num_glyph = %d", numGlyphs);
758        ALOGD("         -- isDevKernText = %d", paint->isDevKernText());
759        ALOGD("         -- initial totalAdvance = %f", totalAdvance);
760
761        logGlyphs(mBuffer);
762#endif
763
764        for (size_t i = 0; i < numGlyphs; i++) {
765            size_t cluster = info[i].cluster - start;
766            float xAdvance = HBFixedToFloat(positions[i].x_advance);
767            outAdvances->replaceAt(outAdvances->itemAt(cluster) + xAdvance, cluster);
768            outGlyphs->add(info[i].codepoint + glyphBaseCount);
769            float xo = HBFixedToFloat(positions[i].x_offset);
770            float yo = -HBFixedToFloat(positions[i].y_offset);
771            outPos->add(totalAdvance + xo + yo * skewX);
772            outPos->add(yo);
773            totalAdvance += xAdvance;
774        }
775    }
776
777    *outTotalAdvance = totalAdvance;
778
779#if DEBUG_GLYPHS
780    ALOGD("         -- final totalAdvance = %f", totalAdvance);
781    ALOGD("-------- End of Script Run --------");
782#endif
783}
784
785/**
786 * Return the first typeface in the logical change, starting with this typeface,
787 * that contains the specified unichar, or NULL if none is found.
788 *
789 * Note that this function does _not_ increment the reference count on the typeface, as the
790 * assumption is that its lifetime is managed elsewhere - in particular, the fallback typefaces
791 * for the default font live in a global cache.
792 */
793SkTypeface* TextLayoutShaper::typefaceForScript(const SkPaint* paint, SkTypeface* typeface,
794        hb_script_t script) {
795    SkTypeface::Style currentStyle = SkTypeface::kNormal;
796    if (typeface) {
797        currentStyle = typeface->style();
798    }
799    typeface = SkCreateTypefaceForScriptNG(script, currentStyle);
800#if DEBUG_GLYPHS
801    ALOGD("Using Harfbuzz Script %d, Style %d", script, currentStyle);
802#endif
803    return typeface;
804}
805
806bool TextLayoutShaper::isComplexScript(hb_script_t script) {
807    switch (script) {
808    case HB_SCRIPT_COMMON:
809    case HB_SCRIPT_GREEK:
810    case HB_SCRIPT_CYRILLIC:
811    case HB_SCRIPT_HANGUL:
812    case HB_SCRIPT_INHERITED:
813        return false;
814    default:
815        return true;
816    }
817}
818
819size_t TextLayoutShaper::shapeFontRun(const SkPaint* paint) {
820    // Update Harfbuzz Shaper
821
822    SkTypeface* typeface = paint->getTypeface();
823
824    // Get the glyphs base count for offsetting the glyphIDs returned by Harfbuzz
825    // This is needed as the Typeface used for shaping can be not the default one
826    // when we are shaping any script that needs to use a fallback Font.
827    // If we are a "common" script we dont need to shift
828    size_t baseGlyphCount = 0;
829    hb_codepoint_t firstUnichar = 0;
830    if (isComplexScript(hb_buffer_get_script(mBuffer))) {
831        unsigned int numGlyphs;
832        hb_glyph_info_t* info = hb_buffer_get_glyph_infos(mBuffer, &numGlyphs);
833        for (size_t i = 0; i < numGlyphs; i++) {
834            firstUnichar = info[i].codepoint;
835            if (firstUnichar != ' ') {
836                break;
837            }
838        }
839        baseGlyphCount = paint->getBaseGlyphCount(firstUnichar);
840    }
841
842    if (baseGlyphCount != 0) {
843        typeface = typefaceForScript(paint, typeface, hb_buffer_get_script(mBuffer));
844        if (!typeface) {
845            typeface = mDefaultTypeface;
846            SkSafeRef(typeface);
847#if DEBUG_GLYPHS
848            ALOGD("Using Default Typeface");
849#endif
850        }
851    } else {
852        if (!typeface) {
853            typeface = mDefaultTypeface;
854#if DEBUG_GLYPHS
855            ALOGD("Using Default Typeface");
856#endif
857        }
858        SkSafeRef(typeface);
859    }
860
861    mShapingPaint.setTypeface(typeface);
862    hb_face_t* face = referenceCachedHBFace(typeface);
863
864    float sizeY = paint->getTextSize();
865    float sizeX = sizeY * paint->getTextScaleX();
866    hb_font_t* font = createFont(face, &mShapingPaint, sizeX, sizeY);
867    hb_face_destroy(face);
868
869#if DEBUG_GLYPHS
870    ALOGD("Run typeface = %p, uniqueID = %d, face = %p",
871            typeface, typeface->uniqueID(), face);
872#endif
873    SkSafeUnref(typeface);
874
875    hb_shape(font, mBuffer, NULL, 0);
876    hb_font_destroy(font);
877
878    return baseGlyphCount;
879}
880
881hb_face_t* TextLayoutShaper::referenceCachedHBFace(SkTypeface* typeface) {
882    SkFontID fontId = typeface->uniqueID();
883    ssize_t index = mCachedHBFaces.indexOfKey(fontId);
884    if (index >= 0) {
885        return hb_face_reference(mCachedHBFaces.valueAt(index));
886    }
887    // TODO: destroy function
888    hb_face_t* face = hb_face_create_for_tables(harfbuzzSkiaReferenceTable, typeface, NULL);
889#if DEBUG_GLYPHS
890    ALOGD("Created HB_NewFace %p from paint typeface = %p", face, typeface);
891#endif
892    mCachedHBFaces.add(fontId, face);
893    return hb_face_reference(face);
894}
895
896void TextLayoutShaper::purgeCaches() {
897    size_t cacheSize = mCachedHBFaces.size();
898    for (size_t i = 0; i < cacheSize; i++) {
899        hb_face_destroy(mCachedHBFaces.valueAt(i));
900    }
901    mCachedHBFaces.clear();
902    unrefTypefaces();
903    init();
904}
905
906TextLayoutEngine::TextLayoutEngine() {
907    mShaper = new TextLayoutShaper();
908#if USE_TEXT_LAYOUT_CACHE
909    mTextLayoutCache = new TextLayoutCache(mShaper);
910#else
911    mTextLayoutCache = NULL;
912#endif
913}
914
915TextLayoutEngine::~TextLayoutEngine() {
916    delete mTextLayoutCache;
917    delete mShaper;
918}
919
920sp<TextLayoutValue> TextLayoutEngine::getValue(const SkPaint* paint, const jchar* text,
921        jint start, jint count, jint contextCount, jint dirFlags) {
922    sp<TextLayoutValue> value;
923#if USE_TEXT_LAYOUT_CACHE
924    value = mTextLayoutCache->getValue(paint, text, start, count,
925            contextCount, dirFlags);
926    if (value == NULL) {
927        ALOGE("Cannot get TextLayoutCache value for text = '%s'",
928                String8(text + start, count).string());
929    }
930#else
931    value = new TextLayoutValue(count);
932    mShaper->computeValues(value.get(), paint,
933            reinterpret_cast<const UChar*>(text), start, count, contextCount, dirFlags);
934#endif
935    return value;
936}
937
938void TextLayoutEngine::purgeCaches() {
939#if USE_TEXT_LAYOUT_CACHE
940    mTextLayoutCache->purgeCaches();
941#if DEBUG_GLYPHS
942    ALOGD("Purged TextLayoutEngine caches");
943#endif
944#endif
945}
946
947
948} // namespace android
949