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