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 #include "minikin/Layout.h"
18
19 #include <gtest/gtest.h>
20
21 #include "minikin/LayoutCache.h"
22
23 #include "FontTestUtils.h"
24 #include "LocaleListCache.h"
25 #include "UnicodeUtils.h"
26
27 namespace minikin {
28
29 class TestableLayoutCache : public LayoutCache {
30 public:
TestableLayoutCache(uint32_t maxEntries)31 TestableLayoutCache(uint32_t maxEntries) : LayoutCache(maxEntries) {}
32 using LayoutCache::getCacheSize;
33 };
34
35 class LayoutCapture {
36 public:
LayoutCapture()37 LayoutCapture() {}
38
operator ()(const LayoutPiece & layout,const MinikinPaint &,const MinikinRect & bounds)39 void operator()(const LayoutPiece& layout, const MinikinPaint& /* dir */,
40 const MinikinRect& bounds) {
41 mLayout = &layout;
42 mBounds = bounds;
43 }
44
get() const45 const LayoutPiece* get() const { return mLayout; }
bounds() const46 const MinikinRect& bounds() const { return mBounds; }
47
48 private:
49 const LayoutPiece* mLayout;
50 MinikinRect mBounds;
51 };
52
TEST(LayoutCacheTest,cacheHitTest)53 TEST(LayoutCacheTest, cacheHitTest) {
54 auto text = utf8ToUtf16("android");
55 Range range(0, text.size());
56 MinikinPaint paint(buildFontCollection("Ascii.ttf"));
57
58 TestableLayoutCache layoutCache(10);
59
60 LayoutCapture layout1;
61 layoutCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT,
62 EndHyphenEdit::NO_EDIT, false, layout1);
63
64 LayoutCapture layout2;
65 layoutCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT,
66 EndHyphenEdit::NO_EDIT, false, layout2);
67
68 EXPECT_EQ(layout1.get(), layout2.get());
69 }
70
TEST(LayoutCacheTest,cacheMissTest)71 TEST(LayoutCacheTest, cacheMissTest) {
72 auto text1 = utf8ToUtf16("android");
73 auto text2 = utf8ToUtf16("ANDROID");
74 MinikinPaint paint(buildFontCollection("Ascii.ttf"));
75
76 TestableLayoutCache layoutCache(10);
77
78 LayoutCapture layout1;
79 LayoutCapture layout2;
80
81 {
82 SCOPED_TRACE("Different text");
83 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
84 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout1);
85 layoutCache.getOrCreate(text2, Range(0, text2.size()), paint, false /* LTR */,
86 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout2);
87 EXPECT_NE(layout1.get(), layout2.get());
88 }
89 {
90 SCOPED_TRACE("Different range");
91 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
92 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout1);
93 layoutCache.getOrCreate(text1, Range(1, text1.size()), paint, false /* LTR */,
94 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout2);
95 EXPECT_NE(layout1.get(), layout2.get());
96 }
97 {
98 SCOPED_TRACE("Different text");
99 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
100 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout1);
101 layoutCache.getOrCreate(text2, Range(0, text2.size()), paint, false /* LTR */,
102 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout2);
103 EXPECT_NE(layout1.get(), layout2.get());
104 }
105 {
106 SCOPED_TRACE("Different direction");
107 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
108 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout1);
109 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, true /* RTL */,
110 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout2);
111 EXPECT_NE(layout1.get(), layout2.get());
112 }
113 {
114 SCOPED_TRACE("Different start hyphenation");
115 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
116 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout1);
117 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
118 StartHyphenEdit::INSERT_HYPHEN, EndHyphenEdit::NO_EDIT, false,
119 layout2);
120 EXPECT_NE(layout1.get(), layout2.get());
121 }
122 {
123 SCOPED_TRACE("Different end hyphen");
124 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
125 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout1);
126 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
127 StartHyphenEdit::NO_EDIT, EndHyphenEdit::INSERT_HYPHEN, false,
128 layout2);
129 EXPECT_NE(layout1.get(), layout2.get());
130 }
131 {
132 SCOPED_TRACE("Different collection");
133 MinikinPaint paint1(buildFontCollection("Ascii.ttf"));
134 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
135 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout1);
136 MinikinPaint paint2(buildFontCollection("Emoji.ttf"));
137 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
138 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout2);
139 EXPECT_NE(layout1.get(), layout2.get());
140 }
141 {
142 SCOPED_TRACE("Different size");
143 auto collection = buildFontCollection("Ascii.ttf");
144 MinikinPaint paint1(collection);
145 paint1.size = 10.0f;
146 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
147 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout1);
148 MinikinPaint paint2(collection);
149 paint2.size = 20.0f;
150 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
151 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout2);
152 EXPECT_NE(layout1.get(), layout2.get());
153 }
154 {
155 SCOPED_TRACE("Different scale X");
156 auto collection = buildFontCollection("Ascii.ttf");
157 MinikinPaint paint1(collection);
158 paint1.scaleX = 1.0f;
159 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
160 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout1);
161 MinikinPaint paint2(collection);
162 paint2.scaleX = 2.0f;
163 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
164 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout2);
165 EXPECT_NE(layout1.get(), layout2.get());
166 }
167 {
168 SCOPED_TRACE("Different skew X");
169 auto collection = buildFontCollection("Ascii.ttf");
170 MinikinPaint paint1(collection);
171 paint1.skewX = 1.0f;
172 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
173 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout1);
174 MinikinPaint paint2(collection);
175 paint2.skewX = 2.0f;
176 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
177 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout2);
178 EXPECT_NE(layout1.get(), layout2.get());
179 }
180 {
181 SCOPED_TRACE("Different letter spacing");
182 auto collection = buildFontCollection("Ascii.ttf");
183 MinikinPaint paint1(collection);
184 paint1.letterSpacing = 0.0f;
185 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
186 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout1);
187 MinikinPaint paint2(collection);
188 paint2.letterSpacing = 1.0f;
189 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
190 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout2);
191 EXPECT_NE(layout1.get(), layout2.get());
192 }
193 {
194 SCOPED_TRACE("Different word spacing");
195 auto collection = buildFontCollection("Ascii.ttf");
196 MinikinPaint paint1(collection);
197 paint1.wordSpacing = 0.0f;
198 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
199 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout1);
200 MinikinPaint paint2(collection);
201 paint2.wordSpacing = 1.0f;
202 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
203 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout2);
204 EXPECT_NE(layout1.get(), layout2.get());
205 }
206 {
207 SCOPED_TRACE("Different paint flags");
208 auto collection = buildFontCollection("Ascii.ttf");
209 MinikinPaint paint1(collection);
210 paint1.fontFlags = 0;
211 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
212 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout1);
213 MinikinPaint paint2(collection);
214 paint2.fontFlags = LinearMetrics_Flag;
215 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
216 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout2);
217 EXPECT_NE(layout1.get(), layout2.get());
218 }
219 {
220 SCOPED_TRACE("Different locale list ID");
221 auto collection = buildFontCollection("Ascii.ttf");
222 MinikinPaint paint1(collection);
223 paint1.localeListId = LocaleListCache::getId("en-US");
224 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
225 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout1);
226 MinikinPaint paint2(collection);
227 paint2.localeListId = LocaleListCache::getId("ja-JP");
228 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
229 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout2);
230 EXPECT_NE(layout1.get(), layout2.get());
231 }
232 {
233 SCOPED_TRACE("Different family variant");
234 auto collection = buildFontCollection("Ascii.ttf");
235 MinikinPaint paint1(collection);
236 paint1.familyVariant = FamilyVariant::DEFAULT;
237 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
238 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout1);
239 MinikinPaint paint2(collection);
240 paint2.familyVariant = FamilyVariant::COMPACT;
241 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
242 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout2);
243 EXPECT_NE(layout1.get(), layout2.get());
244 }
245 {
246 SCOPED_TRACE("Different font feature settings");
247 auto collection = buildFontCollection("Ascii.ttf");
248 MinikinPaint paint1(collection);
249 paint1.fontFeatureSettings = FontFeature::parse("");
250 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
251 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout1);
252 MinikinPaint paint2(collection);
253 paint2.fontFeatureSettings = FontFeature::parse("'liga' on");
254 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
255 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout2);
256 EXPECT_NE(layout1.get(), layout2.get());
257 }
258 }
259
TEST(LayoutCacheTest,cacheOverflowTest)260 TEST(LayoutCacheTest, cacheOverflowTest) {
261 auto text = utf8ToUtf16("android");
262 Range range(0, text.size());
263 MinikinPaint paint(buildFontCollection("Ascii.ttf"));
264
265 TestableLayoutCache layoutCache(5);
266
267 LayoutCapture layout1;
268 layoutCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT,
269 EndHyphenEdit::NO_EDIT, false, layout1);
270
271 for (char c = 'a'; c <= 'z'; c++) {
272 auto text1 = utf8ToUtf16(std::string(10, c));
273 LayoutCapture layout2;
274 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
275 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, false, layout2);
276 }
277
278 LayoutCapture layout3;
279 layoutCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT,
280 EndHyphenEdit::NO_EDIT, false, layout3);
281 EXPECT_NE(layout1.get(), layout3.get());
282 }
283
TEST(LayoutCacheTest,cacheLengthLimitTest)284 TEST(LayoutCacheTest, cacheLengthLimitTest) {
285 auto text = utf8ToUtf16(std::string(130, 'a'));
286 Range range(0, text.size());
287 MinikinPaint paint(buildFontCollection("Ascii.ttf"));
288
289 TestableLayoutCache layoutCache(140);
290
291 LayoutCapture layout;
292 layoutCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT,
293 EndHyphenEdit::NO_EDIT, false, layout);
294
295 EXPECT_EQ(layoutCache.getCacheSize(), 0u);
296 }
297
TEST(LayoutCacheTest,boundsCalculation)298 TEST(LayoutCacheTest, boundsCalculation) {
299 auto text1 = utf8ToUtf16("android");
300 MinikinPaint paint(buildFontCollection("Ascii.ttf"));
301
302 TestableLayoutCache layoutCache(10);
303
304 LayoutCapture layout1;
305 LayoutCapture layout2;
306
307 {
308 SCOPED_TRACE("Bounds calculation should be treated different layout cache entry");
309 layoutCache.clear();
310 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
311 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT,
312 false /* calculateBounds */, layout1);
313 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
314 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT,
315 true /* calculateBounds */, layout2);
316 EXPECT_NE(layout1.get(), layout2.get());
317 EXPECT_FALSE(layout1.bounds().isValid());
318 EXPECT_TRUE(layout2.bounds().isValid());
319 }
320 {
321 SCOPED_TRACE("Bounds calculated entry can be used for the non-bounds request.");
322 layoutCache.clear();
323 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
324 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT,
325 true /* calculateBounds */, layout1);
326 layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
327 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT,
328 false /* calculateBounds */, layout2);
329 EXPECT_EQ(layout1.get(), layout2.get());
330 EXPECT_TRUE(layout1.bounds().isValid());
331 }
332 }
333
334 } // namespace minikin
335