1 /*
2  * Copyright (C) 2018 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 MINIKIN_LAYOUT_CACHE_H
18 #define MINIKIN_LAYOUT_CACHE_H
19 
20 #include <utils/LruCache.h>
21 
22 #include <mutex>
23 
24 #include "minikin/Constants.h"
25 #include "minikin/FontCollection.h"
26 #include "minikin/Hasher.h"
27 #include "minikin/LayoutCore.h"
28 #include "minikin/MinikinPaint.h"
29 
30 #ifdef _WIN32
31 #include <io.h>
32 #endif
33 
34 namespace minikin {
35 // Layout cache datatypes
36 class LayoutCacheKey {
37 public:
LayoutCacheKey(const U16StringPiece & text,const Range & range,const MinikinPaint & paint,bool dir,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen)38     LayoutCacheKey(const U16StringPiece& text, const Range& range, const MinikinPaint& paint,
39                    bool dir, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen)
40             : mChars(text.data()),
41               mNchars(text.size()),
42               mStart(range.getStart()),
43               mCount(range.getLength()),
44               mId(paint.font->getId()),
45               mStyle(paint.fontStyle),
46               mSize(paint.size),
47               mScaleX(paint.scaleX),
48               mSkewX(paint.skewX),
49               mLetterSpacing(paint.letterSpacing),
50               mWordSpacing(paint.wordSpacing),
51               mFontFlags(paint.fontFlags),
52               mLocaleListId(paint.localeListId),
53               mFamilyVariant(paint.familyVariant),
54               mStartHyphen(startHyphen),
55               mEndHyphen(endHyphen),
56               mIsRtl(dir),
57               mFontFeatureSettings(paint.fontFeatureSettings),
58               mHash(computeHash()) {}
59 
60     bool operator==(const LayoutCacheKey& o) const {
61         return mId == o.mId && mStart == o.mStart && mCount == o.mCount && mStyle == o.mStyle &&
62                mSize == o.mSize && mScaleX == o.mScaleX && mSkewX == o.mSkewX &&
63                mLetterSpacing == o.mLetterSpacing && mWordSpacing == o.mWordSpacing &&
64                mFontFlags == o.mFontFlags && mLocaleListId == o.mLocaleListId &&
65                mFamilyVariant == o.mFamilyVariant && mStartHyphen == o.mStartHyphen &&
66                mEndHyphen == o.mEndHyphen && mIsRtl == o.mIsRtl && mNchars == o.mNchars &&
67                mFontFeatureSettings == o.mFontFeatureSettings &&
68                !memcmp(mChars, o.mChars, mNchars * sizeof(uint16_t));
69     }
70 
hash()71     android::hash_t hash() const { return mHash; }
72 
copyText()73     void copyText() {
74         uint16_t* charsCopy = new uint16_t[mNchars];
75         memcpy(charsCopy, mChars, mNchars * sizeof(uint16_t));
76         mChars = charsCopy;
77     }
freeText()78     void freeText() {
79         delete[] mChars;
80         mChars = NULL;
81         mFontFeatureSettings.clear();
82     }
83 
getMemoryUsage()84     uint32_t getMemoryUsage() const { return sizeof(LayoutCacheKey) + sizeof(uint16_t) * mNchars; }
85 
86 private:
87     const uint16_t* mChars;
88     uint32_t mNchars;
89     uint32_t mStart;
90     uint32_t mCount;
91     uint32_t mId;  // for the font collection
92     FontStyle mStyle;
93     float mSize;
94     float mScaleX;
95     float mSkewX;
96     float mLetterSpacing;
97     float mWordSpacing;
98     int32_t mFontFlags;
99     uint32_t mLocaleListId;
100     FamilyVariant mFamilyVariant;
101     StartHyphenEdit mStartHyphen;
102     EndHyphenEdit mEndHyphen;
103     bool mIsRtl;
104     std::vector<FontFeature> mFontFeatureSettings;
105     // Note: any fields added to MinikinPaint must also be reflected here.
106     // TODO: language matching (possibly integrate into style)
107     android::hash_t mHash;
108 
computeHash()109     android::hash_t computeHash() const {
110         return Hasher()
111                 .update(mId)
112                 .update(mStart)
113                 .update(mCount)
114                 .update(mStyle.identifier())
115                 .update(mSize)
116                 .update(mScaleX)
117                 .update(mSkewX)
118                 .update(mLetterSpacing)
119                 .update(mWordSpacing)
120                 .update(mFontFlags)
121                 .update(mLocaleListId)
122                 .update(static_cast<uint8_t>(mFamilyVariant))
123                 .update(packHyphenEdit(mStartHyphen, mEndHyphen))
124                 .update(mIsRtl)
125                 .updateShorts(mChars, mNchars)
126                 .update(mFontFeatureSettings)
127                 .hash();
128     }
129 };
130 
131 // A class holds a layout information and bounding box of it. The bounding box can be invalid if not
132 // calculated.
133 class LayoutSlot {
134 public:
LayoutSlot(LayoutPiece && layout)135     LayoutSlot(LayoutPiece&& layout)
136             : mLayout(std::move(layout)), mBounds(MinikinRect::makeInvalid()) {}
LayoutSlot(LayoutPiece && layout,MinikinRect && bounds)137     LayoutSlot(LayoutPiece&& layout, MinikinRect&& bounds)
138             : mLayout(std::move(layout)), mBounds(std::move(bounds)) {}
LayoutSlot(const LayoutPiece & layout,const MinikinRect & bounds)139     LayoutSlot(const LayoutPiece& layout, const MinikinRect& bounds)
140             : mLayout(layout), mBounds(bounds) {}
141 
142     const LayoutPiece mLayout;
143     MinikinRect mBounds;
144 
145 private:
146     LayoutSlot(const LayoutSlot&) = delete;
147     LayoutSlot& operator=(const LayoutSlot&) = delete;
148     LayoutSlot(LayoutSlot&&) = delete;
149     LayoutSlot& operator=(LayoutSlot&&) = delete;
150 };
151 
152 class LayoutCache : private android::OnEntryRemoved<LayoutCacheKey, LayoutSlot*> {
153 public:
clear()154     void clear() {
155         std::lock_guard<std::mutex> lock(mMutex);
156         mCache.clear();
157     }
158 
159     // Do not use LayoutCache inside the callback function, otherwise dead-lock may happen.
160     template <typename F>
getOrCreate(const U16StringPiece & text,const Range & range,const MinikinPaint & paint,bool dir,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen,bool boundsCalculation,F & f)161     void getOrCreate(const U16StringPiece& text, const Range& range, const MinikinPaint& paint,
162                      bool dir, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
163                      bool boundsCalculation, F& f) {
164         LayoutCacheKey key(text, range, paint, dir, startHyphen, endHyphen);
165         if (paint.skipCache() || range.getLength() >= CHAR_LIMIT_FOR_CACHE) {
166             LayoutPiece piece(text, range, dir, paint, startHyphen, endHyphen);
167             if (boundsCalculation) {
168                 f(piece, paint, LayoutPiece::calculateBounds(piece, paint));
169             } else {
170                 f(piece, paint, MinikinRect::makeInvalid());
171             }
172             return;
173         }
174 
175         LayoutSlot* cachedSlot;
176         {
177             std::lock_guard<std::mutex> lock(mMutex);
178             cachedSlot = mCache.get(key);
179 
180             if (cachedSlot != nullptr) {
181                 if (boundsCalculation && !cachedSlot->mBounds.isValid()) {
182                     MinikinRect bounds = LayoutPiece::calculateBounds(cachedSlot->mLayout, paint);
183                     LayoutPiece lp = cachedSlot->mLayout;
184                     f(lp, paint, bounds);
185                     cachedSlot->mBounds = bounds;
186                 } else {
187                     f(cachedSlot->mLayout, paint, cachedSlot->mBounds);
188                 }
189                 return;
190             }
191         }
192         // Doing text layout takes long time, so releases the mutex during doing layout.
193         // Don't care even if we do the same layout in other thred.
194         key.copyText();
195 
196         std::unique_ptr<LayoutSlot> slot;
197         if (boundsCalculation) {
198             LayoutPiece lp = LayoutPiece(text, range, dir, paint, startHyphen, endHyphen);
199             MinikinRect rect = LayoutPiece::calculateBounds(lp, paint);
200 
201             slot = std::make_unique<LayoutSlot>(std::move(lp), std::move(rect));
202         } else {
203             slot = std::make_unique<LayoutSlot>(
204                     LayoutPiece(text, range, dir, paint, startHyphen, endHyphen));
205         }
206 
207         f(slot->mLayout, paint, slot->mBounds);
208         {
209             std::lock_guard<std::mutex> lock(mMutex);
210             mCache.put(key, slot.release());
211         }
212     }
213 
getInstance()214     static LayoutCache& getInstance() {
215         static LayoutCache cache(kMaxEntries);
216         return cache;
217     }
218 
219 protected:
LayoutCache(uint32_t maxEntries)220     LayoutCache(uint32_t maxEntries) : mCache(maxEntries) {
221         mCache.setOnEntryRemovedListener(this);
222     }
223 
getCacheSize()224     uint32_t getCacheSize() {
225         std::lock_guard<std::mutex> lock(mMutex);
226         return mCache.size();
227     }
228 
229 private:
230     // callback for OnEntryRemoved
operator()231     void operator()(LayoutCacheKey& key, LayoutSlot*& value) {
232         key.freeText();
233         delete value;
234     }
235 
236     android::LruCache<LayoutCacheKey, LayoutSlot*> mCache GUARDED_BY(mMutex);
237 
238     // static const size_t kMaxEntries = LruCache<LayoutCacheKey, Layout*>::kUnlimitedCapacity;
239 
240     // TODO: eviction based on memory footprint; for now, we just use a constant
241     // number of strings
242     static const size_t kMaxEntries = 5000;
243 
244     std::mutex mMutex;
245 };
246 
hash_type(const LayoutCacheKey & key)247 inline android::hash_t hash_type(const LayoutCacheKey& key) {
248     return key.hash();
249 }
250 
251 }  // namespace minikin
252 #endif  // MINIKIN_LAYOUT_CACHE_H
253