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