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 &)39     void operator()(const LayoutPiece& layout, const MinikinPaint& /* dir */) { mLayout = &layout; }
40 
get() const41     const LayoutPiece* get() const { return mLayout; }
42 
43 private:
44     const LayoutPiece* mLayout;
45 };
46 
TEST(LayoutCacheTest,cacheHitTest)47 TEST(LayoutCacheTest, cacheHitTest) {
48     auto text = utf8ToUtf16("android");
49     Range range(0, text.size());
50     MinikinPaint paint(buildFontCollection("Ascii.ttf"));
51 
52     TestableLayoutCache layoutCache(10);
53 
54     LayoutCapture layout1;
55     layoutCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT,
56                             EndHyphenEdit::NO_EDIT, layout1);
57 
58     LayoutCapture layout2;
59     layoutCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT,
60                             EndHyphenEdit::NO_EDIT, layout2);
61 
62     EXPECT_EQ(layout1.get(), layout2.get());
63 }
64 
TEST(LayoutCacheTest,cacheMissTest)65 TEST(LayoutCacheTest, cacheMissTest) {
66     auto text1 = utf8ToUtf16("android");
67     auto text2 = utf8ToUtf16("ANDROID");
68     MinikinPaint paint(buildFontCollection("Ascii.ttf"));
69 
70     TestableLayoutCache layoutCache(10);
71 
72     LayoutCapture layout1;
73     LayoutCapture layout2;
74 
75     {
76         SCOPED_TRACE("Different text");
77         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
78                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
79         layoutCache.getOrCreate(text2, Range(0, text2.size()), paint, false /* LTR */,
80                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
81         EXPECT_NE(layout1.get(), layout2.get());
82     }
83     {
84         SCOPED_TRACE("Different range");
85         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
86                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
87         layoutCache.getOrCreate(text1, Range(1, text1.size()), paint, false /* LTR */,
88                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
89         EXPECT_NE(layout1.get(), layout2.get());
90     }
91     {
92         SCOPED_TRACE("Different text");
93         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
94                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
95         layoutCache.getOrCreate(text2, Range(0, text2.size()), paint, false /* LTR */,
96                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
97         EXPECT_NE(layout1.get(), layout2.get());
98     }
99     {
100         SCOPED_TRACE("Different direction");
101         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
102                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
103         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, true /* RTL */,
104                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
105         EXPECT_NE(layout1.get(), layout2.get());
106     }
107     {
108         SCOPED_TRACE("Different start hyphenation");
109         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
110                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
111         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
112                                 StartHyphenEdit::INSERT_HYPHEN, EndHyphenEdit::NO_EDIT, layout2);
113         EXPECT_NE(layout1.get(), layout2.get());
114     }
115     {
116         SCOPED_TRACE("Different end hyphen");
117         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
118                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
119         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
120                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::INSERT_HYPHEN, layout2);
121         EXPECT_NE(layout1.get(), layout2.get());
122     }
123     {
124         SCOPED_TRACE("Different collection");
125         MinikinPaint paint1(buildFontCollection("Ascii.ttf"));
126         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
127                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
128         MinikinPaint paint2(buildFontCollection("Emoji.ttf"));
129         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
130                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
131         EXPECT_NE(layout1.get(), layout2.get());
132     }
133     {
134         SCOPED_TRACE("Different size");
135         auto collection = buildFontCollection("Ascii.ttf");
136         MinikinPaint paint1(collection);
137         paint1.size = 10.0f;
138         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
139                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
140         MinikinPaint paint2(collection);
141         paint2.size = 20.0f;
142         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
143                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
144         EXPECT_NE(layout1.get(), layout2.get());
145     }
146     {
147         SCOPED_TRACE("Different scale X");
148         auto collection = buildFontCollection("Ascii.ttf");
149         MinikinPaint paint1(collection);
150         paint1.scaleX = 1.0f;
151         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
152                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
153         MinikinPaint paint2(collection);
154         paint2.scaleX = 2.0f;
155         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
156                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
157         EXPECT_NE(layout1.get(), layout2.get());
158     }
159     {
160         SCOPED_TRACE("Different skew X");
161         auto collection = buildFontCollection("Ascii.ttf");
162         MinikinPaint paint1(collection);
163         paint1.skewX = 1.0f;
164         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
165                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
166         MinikinPaint paint2(collection);
167         paint2.skewX = 2.0f;
168         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
169                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
170         EXPECT_NE(layout1.get(), layout2.get());
171     }
172     {
173         SCOPED_TRACE("Different letter spacing");
174         auto collection = buildFontCollection("Ascii.ttf");
175         MinikinPaint paint1(collection);
176         paint1.letterSpacing = 0.0f;
177         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
178                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
179         MinikinPaint paint2(collection);
180         paint2.letterSpacing = 1.0f;
181         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
182                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
183         EXPECT_NE(layout1.get(), layout2.get());
184     }
185     {
186         SCOPED_TRACE("Different word spacing");
187         auto collection = buildFontCollection("Ascii.ttf");
188         MinikinPaint paint1(collection);
189         paint1.wordSpacing = 0.0f;
190         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
191                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
192         MinikinPaint paint2(collection);
193         paint2.wordSpacing = 1.0f;
194         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
195                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
196         EXPECT_NE(layout1.get(), layout2.get());
197     }
198     {
199         SCOPED_TRACE("Different paint flags");
200         auto collection = buildFontCollection("Ascii.ttf");
201         MinikinPaint paint1(collection);
202         paint1.fontFlags = 0;
203         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
204                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
205         MinikinPaint paint2(collection);
206         paint2.fontFlags = LinearMetrics_Flag;
207         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
208                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
209         EXPECT_NE(layout1.get(), layout2.get());
210     }
211     {
212         SCOPED_TRACE("Different locale list ID");
213         auto collection = buildFontCollection("Ascii.ttf");
214         MinikinPaint paint1(collection);
215         paint1.localeListId = LocaleListCache::getId("en-US");
216         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
217                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
218         MinikinPaint paint2(collection);
219         paint2.localeListId = LocaleListCache::getId("ja-JP");
220         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
221                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
222         EXPECT_NE(layout1.get(), layout2.get());
223     }
224     {
225         SCOPED_TRACE("Different family variant");
226         auto collection = buildFontCollection("Ascii.ttf");
227         MinikinPaint paint1(collection);
228         paint1.familyVariant = FamilyVariant::DEFAULT;
229         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
230                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
231         MinikinPaint paint2(collection);
232         paint2.familyVariant = FamilyVariant::COMPACT;
233         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
234                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
235         EXPECT_NE(layout1.get(), layout2.get());
236     }
237     {
238         SCOPED_TRACE("Different font feature settings");
239         auto collection = buildFontCollection("Ascii.ttf");
240         MinikinPaint paint1(collection);
241         paint1.fontFeatureSettings = "";
242         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
243                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
244         MinikinPaint paint2(collection);
245         paint2.fontFeatureSettings = "'liga' on";
246         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
247                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
248         EXPECT_NE(layout1.get(), layout2.get());
249     }
250 }
251 
TEST(LayoutCacheTest,cacheOverflowTest)252 TEST(LayoutCacheTest, cacheOverflowTest) {
253     auto text = utf8ToUtf16("android");
254     Range range(0, text.size());
255     MinikinPaint paint(buildFontCollection("Ascii.ttf"));
256 
257     TestableLayoutCache layoutCache(5);
258 
259     LayoutCapture layout1;
260     layoutCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT,
261                             EndHyphenEdit::NO_EDIT, layout1);
262 
263     for (char c = 'a'; c <= 'z'; c++) {
264         auto text1 = utf8ToUtf16(std::string(10, c));
265         LayoutCapture layout2;
266         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
267                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
268     }
269 
270     LayoutCapture layout3;
271     layoutCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT,
272                             EndHyphenEdit::NO_EDIT, layout3);
273     EXPECT_NE(layout1.get(), layout3.get());
274 }
275 
TEST(LayoutCacheTest,cacheLengthLimitTest)276 TEST(LayoutCacheTest, cacheLengthLimitTest) {
277     auto text = utf8ToUtf16(std::string(130, 'a'));
278     Range range(0, text.size());
279     MinikinPaint paint(buildFontCollection("Ascii.ttf"));
280 
281     TestableLayoutCache layoutCache(140);
282 
283     LayoutCapture layout;
284     layoutCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT,
285                             EndHyphenEdit::NO_EDIT, layout);
286 
287     EXPECT_EQ(layoutCache.getCacheSize(), 0u);
288 }
289 
290 }  // namespace minikin
291