TextLayoutCache.h revision eee49c699c035ffba188417489f40d34f587d65c
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#ifndef ANDROID_TEXT_LAYOUT_CACHE_H
18#define ANDROID_TEXT_LAYOUT_CACHE_H
19
20#include "RtlProperties.h"
21
22#include <stddef.h>
23#include <utils/threads.h>
24#include <utils/String16.h>
25#include <utils/GenerationCache.h>
26#include <utils/Compare.h>
27
28#include <SkPaint.h>
29#include <SkTemplates.h>
30#include <SkUtils.h>
31#include <SkScalerContext.h>
32#include <SkAutoKern.h>
33
34#include <unicode/ubidi.h>
35#include <unicode/ushape.h>
36#include "HarfbuzzSkia.h"
37#include "harfbuzz-shaper.h"
38
39#include <android_runtime/AndroidRuntime.h>
40
41#define UNICODE_NOT_A_CHAR              0xffff
42#define UNICODE_ZWSP                    0x200b
43#define UNICODE_FIRST_LOW_SURROGATE     0xdc00
44#define UNICODE_FIRST_HIGH_SURROGATE    0xd800
45#define UNICODE_FIRST_PRIVATE_USE       0xe000
46#define UNICODE_FIRST_RTL_CHAR          0x0590
47
48// Temporary buffer size
49#define CHAR_BUFFER_SIZE 80
50
51// Converts a number of mega-bytes into bytes
52#define MB(s) s * 1024 * 1024
53
54// Define the default cache size in Mb
55#define DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB 0.125f
56
57// Define the interval in number of cache hits between two statistics dump
58#define DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL 100
59
60namespace android {
61
62/**
63 * TextLayoutCacheKey is the Cache key
64 */
65class TextLayoutCacheKey {
66public:
67    TextLayoutCacheKey() : text(NULL), start(0), count(0), contextCount(0),
68            dirFlags(0), textSize(0), typeface(NULL), textSkewX(0), fakeBoldText(false)  {
69    }
70
71    TextLayoutCacheKey(const SkPaint* paint,
72            const UChar* text, size_t start, size_t count,
73            size_t contextCount, int dirFlags) :
74                text(text), start(start), count(count), contextCount(contextCount),
75                dirFlags(dirFlags) {
76        textSize = paint->getTextSize();
77        typeface = paint->getTypeface();
78        textSkewX = paint->getTextSkewX();
79        fakeBoldText = paint->isFakeBoldText();
80    }
81
82    bool operator<(const TextLayoutCacheKey& rhs) const {
83        LTE_INT(count) {
84            LTE_INT(contextCount) {
85                LTE_INT(start) {
86                    LTE_FLOAT(textSize) {
87                        LTE_INT(typeface) {
88                            LTE_INT(textSkewX) {
89                                LTE_INT(fakeBoldText) {
90                                    LTE_INT(dirFlags) {
91                                        return strncmp16(text, rhs.text, contextCount) < 0;
92                                    }
93                                }
94                            }
95                        }
96                    }
97                }
98            }
99        }
100        return false;
101    }
102
103    // We need to copy the text when we insert the key into the cache itself.
104    // We don't need to copy the text when we are only comparing keys.
105    void internalTextCopy() {
106        textCopy.setTo(text, contextCount);
107        text = textCopy.string();
108    }
109
110    /**
111     * Get the size of the Cache key.
112     */
113    size_t getSize() {
114        return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount;
115    }
116
117private:
118    const UChar* text;
119    String16 textCopy;
120    size_t start;
121    size_t count;
122    size_t contextCount;
123    int dirFlags;
124    float textSize;
125    SkTypeface* typeface;
126    float textSkewX;
127    bool fakeBoldText;
128}; // TextLayoutCacheKey
129
130/*
131 * RunAdvanceDescription is the Cache entry
132 */
133class RunAdvanceDescription {
134public:
135    RunAdvanceDescription() {
136        advances = NULL;
137        totalAdvance = 0;
138    }
139
140    ~RunAdvanceDescription() {
141        delete[] advances;
142    }
143
144    void setElapsedTime(uint32_t time) {
145        elapsedTime = time;
146    }
147
148    uint32_t getElapsedTime() {
149        return elapsedTime;
150    }
151
152    void computeAdvances(SkPaint* paint, const UChar* chars, size_t start, size_t count,
153            size_t contextCount, int dirFlags) {
154        advances = new float[count];
155        this->count = count;
156
157#if RTL_USE_HARFBUZZ
158        computeAdvancesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags,
159                advances, &totalAdvance);
160#else
161        computeAdvancesWithICU(paint, chars, start, count, contextCount, dirFlags,
162                advances, &totalAdvance);
163#endif
164#if DEBUG_ADVANCES
165        LOGD("Advances - count=%d - countextCount=%d - totalAdvance=%f - "
166                "adv[0]=%f adv[1]=%f adv[2]=%f adv[3]=%f", count, contextCount, totalAdvance,
167                advances[0], advances[1], advances[2], advances[3]);
168#endif
169    }
170
171    void copyResult(jfloat* outAdvances, jfloat* outTotalAdvance) {
172        memcpy(outAdvances, advances, count * sizeof(jfloat));
173        *outTotalAdvance = totalAdvance;
174    }
175
176    /**
177     * Get the size of the Cache entry
178     */
179    size_t getSize() {
180        return sizeof(RunAdvanceDescription) + sizeof(jfloat) * count;
181    }
182
183    static void setupShaperItem(HB_ShaperItem* shaperItem, HB_FontRec* font, FontData* fontData,
184            SkPaint* paint, const UChar* chars, size_t start, size_t count, size_t contextCount,
185            int dirFlags) {
186        bool isRTL = dirFlags & 0x1;
187
188        font->klass = &harfbuzzSkiaClass;
189        font->userData = 0;
190        // The values which harfbuzzSkiaClass returns are already scaled to
191        // pixel units, so we just set all these to one to disable further
192        // scaling.
193        font->x_ppem = 1;
194        font->y_ppem = 1;
195        font->x_scale = 1;
196        font->y_scale = 1;
197
198        memset(shaperItem, 0, sizeof(*shaperItem));
199        shaperItem->font = font;
200        shaperItem->face = HB_NewFace(shaperItem->font, harfbuzzSkiaGetTable);
201
202        shaperItem->kerning_applied = false;
203
204        // We cannot know, ahead of time, how many glyphs a given script run
205        // will produce. We take a guess that script runs will not produce more
206        // than twice as many glyphs as there are code points plus a bit of
207        // padding and fallback if we find that we are wrong.
208        createGlyphArrays(shaperItem, (contextCount + 2) * 2);
209
210        // Free memory for clusters if needed and recreate the clusters array
211        if (shaperItem->log_clusters) {
212            delete shaperItem->log_clusters;
213        }
214        shaperItem->log_clusters = new unsigned short[contextCount];
215
216        shaperItem->item.pos = start;
217        shaperItem->item.length = count;
218        shaperItem->item.bidiLevel = isRTL;
219        shaperItem->item.script = isRTL ? HB_Script_Arabic : HB_Script_Common;
220
221        shaperItem->string = chars;
222        shaperItem->stringLength = contextCount;
223
224        fontData->typeFace = paint->getTypeface();
225        fontData->textSize = paint->getTextSize();
226        fontData->textSkewX = paint->getTextSkewX();
227        fontData->textScaleX = paint->getTextScaleX();
228        fontData->flags = paint->getFlags();
229        fontData->hinting = paint->getHinting();
230
231        shaperItem->font->userData = fontData;
232    }
233
234    static void shapeWithHarfbuzz(HB_ShaperItem* shaperItem, HB_FontRec* font, FontData* fontData,
235            SkPaint* paint, const UChar* chars, size_t start, size_t count, size_t contextCount,
236            int dirFlags) {
237        // Setup Harfbuzz Shaper
238        setupShaperItem(shaperItem, font, fontData, paint, chars, start, count,
239                contextCount, dirFlags);
240
241        // Shape
242        resetGlyphArrays(shaperItem);
243        while (!HB_ShapeItem(shaperItem)) {
244            // We overflowed our arrays. Resize and retry.
245            // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size.
246            deleteGlyphArrays(shaperItem);
247            createGlyphArrays(shaperItem, shaperItem->num_glyphs << 1);
248            resetGlyphArrays(shaperItem);
249        }
250    }
251
252#define SkAutoKern_AdjustF(prev, next) (((next) - (prev) + 32) >> 6 << 16)
253
254    static int adjust(int prev, int next) {
255        int delta = next - prev;
256        if (delta >= 32) {
257            return -1;
258        }
259        else if (delta < -32) {
260            return +1;
261        }
262        else {
263            return 0;
264        }
265    }
266
267    static void computeAdvancesWithHarfbuzz(SkPaint* paint, const UChar* chars, size_t start,
268            size_t count, size_t contextCount, int dirFlags,
269            jfloat* outAdvances, jfloat* outTotalAdvance) {
270
271        bool isRTL = dirFlags & 0x1;
272
273        HB_ShaperItem shaperItem;
274        HB_FontRec font;
275        FontData fontData;
276        shapeWithHarfbuzz(&shaperItem, &font, &fontData, paint, chars, start, count,
277                contextCount, dirFlags);
278
279#if DEBUG_ADVANCES
280        LOGD("HARFBUZZ -- num_glypth=%d - kerning_applied=%d", shaperItem.num_glyphs, shaperItem.kerning_applied);
281        LOGD("         -- string= '%s'", String8(chars, contextCount).string());
282        LOGD("         -- isDevKernText=%d", paint->isDevKernText());
283#endif
284
285        jfloat totalAdvance = 0;
286
287        for (size_t i = 0; i < count; i++) {
288            totalAdvance += outAdvances[i] = HBFixedToFloat(shaperItem.advances[i]);
289
290#if DEBUG_ADVANCES
291            LOGD("hb-adv = %d - rebased = %f - total = %f", shaperItem.advances[i], outAdvances[i],
292                    totalAdvance);
293#endif
294        }
295
296        deleteGlyphArrays(&shaperItem);
297        HB_FreeFace(shaperItem.face);
298
299        *outTotalAdvance = totalAdvance;
300    }
301
302    static void computeAdvancesWithICU(SkPaint* paint, const UChar* chars, size_t start,
303            size_t count, size_t contextCount, int dirFlags,
304            jfloat* outAdvances, jfloat* outTotalAdvance) {
305
306        SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount);
307        jchar* buffer = tempBuffer.get();
308
309        SkScalar* scalarArray = (SkScalar*)outAdvances;
310
311        // this is where we'd call harfbuzz
312        // for now we just use ushape.c
313        size_t widths;
314        const jchar* text;
315        if (dirFlags & 0x1) { // rtl, call arabic shaping in case
316            UErrorCode status = U_ZERO_ERROR;
317            // Use fixed length since we need to keep start and count valid
318            u_shapeArabic(chars, contextCount, buffer, contextCount,
319                    U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
320                    U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
321                    U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
322            // we shouldn't fail unless there's an out of memory condition,
323            // in which case we're hosed anyway
324            for (int i = start, e = i + count; i < e; ++i) {
325                if (buffer[i] == UNICODE_NOT_A_CHAR) {
326                    buffer[i] = UNICODE_ZWSP; // zero-width-space for skia
327                }
328            }
329            text = buffer + start;
330            widths = paint->getTextWidths(text, count << 1, scalarArray);
331        } else {
332            text = chars + start;
333            widths = paint->getTextWidths(text, count << 1, scalarArray);
334        }
335
336        jfloat totalAdvance = 0;
337        if (widths < count) {
338#if DEBUG_ADVANCES
339        LOGD("ICU -- count=%d", widths);
340#endif
341            // Skia operates on code points, not code units, so surrogate pairs return only
342            // one value. Expand the result so we have one value per UTF-16 code unit.
343
344            // Note, skia's getTextWidth gets confused if it encounters a surrogate pair,
345            // leaving the remaining widths zero.  Not nice.
346            for (size_t i = 0, p = 0; i < widths; ++i) {
347                totalAdvance += outAdvances[p++] = SkScalarToFloat(scalarArray[i]);
348                if (p < count &&
349                        text[p] >= UNICODE_FIRST_LOW_SURROGATE &&
350                        text[p] < UNICODE_FIRST_PRIVATE_USE &&
351                        text[p-1] >= UNICODE_FIRST_HIGH_SURROGATE &&
352                        text[p-1] < UNICODE_FIRST_LOW_SURROGATE) {
353                    outAdvances[p++] = 0;
354                }
355#if DEBUG_ADVANCES
356                LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance);
357#endif
358            }
359        } else {
360#if DEBUG_ADVANCES
361        LOGD("ICU -- count=%d", count);
362#endif
363            for (size_t i = 0; i < count; i++) {
364                totalAdvance += outAdvances[i] = SkScalarToFloat(scalarArray[i]);
365#if DEBUG_ADVANCES
366                LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance);
367#endif
368            }
369        }
370        *outTotalAdvance = totalAdvance;
371    }
372
373private:
374    jfloat* advances;
375    jfloat totalAdvance;
376    size_t count;
377
378    uint32_t elapsedTime;
379
380    static void deleteGlyphArrays(HB_ShaperItem* shaperItem) {
381        delete[] shaperItem->glyphs;
382        delete[] shaperItem->attributes;
383        delete[] shaperItem->advances;
384        delete[] shaperItem->offsets;
385    }
386
387    static void createGlyphArrays(HB_ShaperItem* shaperItem, int size) {
388        shaperItem->glyphs = new HB_Glyph[size];
389        shaperItem->attributes = new HB_GlyphAttributes[size];
390        shaperItem->advances = new HB_Fixed[size];
391        shaperItem->offsets = new HB_FixedPoint[size];
392        shaperItem->num_glyphs = size;
393    }
394
395    static void resetGlyphArrays(HB_ShaperItem* shaperItem) {
396        int size = shaperItem->num_glyphs;
397        // All the types here don't have pointers. It is safe to reset to
398        // zero unless Harfbuzz breaks the compatibility in the future.
399        memset(shaperItem->glyphs, 0, size * sizeof(shaperItem->glyphs[0]));
400        memset(shaperItem->attributes, 0, size * sizeof(shaperItem->attributes[0]));
401        memset(shaperItem->advances, 0, size * sizeof(shaperItem->advances[0]));
402        memset(shaperItem->offsets, 0, size * sizeof(shaperItem->offsets[0]));
403    }
404
405}; // RunAdvanceDescription
406
407
408class TextLayoutCache: public OnEntryRemoved<TextLayoutCacheKey, RunAdvanceDescription*>
409{
410public:
411    TextLayoutCache();
412    TextLayoutCache(uint32_t maxByteSize);
413
414    virtual ~TextLayoutCache();
415
416    bool isInitialized() {
417        return mInitialized;
418    }
419
420    /**
421     * Used as a callback when an entry is removed from the cache.
422     * Do not invoke directly.
423     */
424    void operator()(TextLayoutCacheKey& text, RunAdvanceDescription*& desc);
425
426    /**
427     * Get cache entries
428     */
429    void getRunAdvances(SkPaint* paint, const jchar* text,
430            jint start, jint count, jint contextCount, jint dirFlags,
431            jfloat* outAdvances, jfloat* outTotalAdvance);
432
433    /**
434     * Clear the cache
435     */
436    void clear();
437
438    /**
439     * Sets the maximum size of the cache in bytes.
440     */
441    void setMaxSize(uint32_t maxSize);
442
443    /**
444     * Returns the maximum size of the cache in bytes.
445     */
446    uint32_t getMaxSize();
447
448    /**
449     * Returns the current size of the cache in bytes.
450     */
451    uint32_t getSize();
452
453private:
454    Mutex mLock;
455    bool mInitialized;
456
457    GenerationCache<TextLayoutCacheKey, RunAdvanceDescription*> mCache;
458
459    uint32_t mSize;
460    uint32_t mMaxSize;
461
462    uint32_t mCacheHitCount;
463    uint64_t mNanosecondsSaved;
464
465    uint64_t mCacheStartTime;
466
467    RtlDebugLevel mDebugLevel;
468    bool mDebugEnabled;
469
470    /*
471     * Class initialization
472     */
473    void init();
474
475    /**
476     * Remove oldest entries until we are having enough space
477     */
478    void removeOldests();
479
480    /**
481     * Dump Cache statistics
482     */
483    void dumpCacheStats();
484}; // TextLayoutCache
485
486} // namespace android
487#endif /* ANDROID_TEXT_LAYOUT_CACHE_H */
488
489