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