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