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