1 /*
2  * Copyright (C) 2016 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/FontCollection.h"
22 #include "minikin/LayoutPieces.h"
23 
24 #include "FontTestUtils.h"
25 #include "UnicodeUtils.h"
26 
27 namespace minikin {
28 
expectAdvances(const std::vector<float> & expected,const std::vector<float> & advances)29 static void expectAdvances(const std::vector<float>& expected, const std::vector<float>& advances) {
30     EXPECT_LE(expected.size(), advances.size());
31     for (size_t i = 0; i < expected.size(); ++i) {
32         EXPECT_EQ(expected[i], advances[i])
33                 << i << "th element is different. Expected: " << expected[i]
34                 << ", Actual: " << advances[i];
35     }
36 }
37 
38 class LayoutTest : public testing::Test {
39 protected:
LayoutTest()40     LayoutTest() : mCollection(nullptr) {}
41 
~LayoutTest()42     virtual ~LayoutTest() {}
43 
SetUp()44     virtual void SetUp() override { mCollection = buildFontCollection("Ascii.ttf"); }
45 
TearDown()46     virtual void TearDown() override {}
47 
48     std::shared_ptr<FontCollection> mCollection;
49 };
50 
TEST_F(LayoutTest,doLayoutTest)51 TEST_F(LayoutTest, doLayoutTest) {
52     MinikinPaint paint(mCollection);
53     paint.size = 10.0f;  // make 1em = 10px
54     MinikinRect rect;
55     std::vector<float> expectedValues;
56 
57     std::vector<uint16_t> text;
58 
59     // The mock implementation returns 10.0f advance and 0,0-10x10 bounds for all glyph.
60     {
61         SCOPED_TRACE("one word");
62         text = utf8ToUtf16("oneword");
63         Range range(0, text.size());
64         Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
65                       EndHyphenEdit::NO_EDIT);
66         EXPECT_EQ(70.0f, layout.getAdvance());
67         layout.getBounds(&rect);
68         EXPECT_EQ(0.0f, rect.mLeft);
69         EXPECT_EQ(10.0f, rect.mTop);
70         EXPECT_EQ(70.0f, rect.mRight);
71         EXPECT_EQ(0.0f, rect.mBottom);
72         expectedValues.resize(text.size());
73         for (size_t i = 0; i < expectedValues.size(); ++i) {
74             expectedValues[i] = 10.0f;
75         }
76         expectAdvances(expectedValues, layout.getAdvances());
77     }
78     {
79         SCOPED_TRACE("two words");
80         text = utf8ToUtf16("two words");
81         Range range(0, text.size());
82         Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
83                       EndHyphenEdit::NO_EDIT);
84         EXPECT_EQ(90.0f, layout.getAdvance());
85         layout.getBounds(&rect);
86         EXPECT_EQ(0.0f, rect.mLeft);
87         EXPECT_EQ(10.0f, rect.mTop);
88         EXPECT_EQ(90.0f, rect.mRight);
89         EXPECT_EQ(0.0f, rect.mBottom);
90         expectedValues.resize(text.size());
91         for (size_t i = 0; i < expectedValues.size(); ++i) {
92             expectedValues[i] = 10.0f;
93         }
94         expectAdvances(expectedValues, layout.getAdvances());
95     }
96     {
97         SCOPED_TRACE("three words");
98         text = utf8ToUtf16("three words test");
99         Range range(0, text.size());
100         Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
101                       EndHyphenEdit::NO_EDIT);
102         EXPECT_EQ(160.0f, layout.getAdvance());
103         layout.getBounds(&rect);
104         EXPECT_EQ(0.0f, rect.mLeft);
105         EXPECT_EQ(10.0f, rect.mTop);
106         EXPECT_EQ(160.0f, rect.mRight);
107         EXPECT_EQ(0.0f, rect.mBottom);
108         expectedValues.resize(text.size());
109         for (size_t i = 0; i < expectedValues.size(); ++i) {
110             expectedValues[i] = 10.0f;
111         }
112         expectAdvances(expectedValues, layout.getAdvances());
113     }
114     {
115         SCOPED_TRACE("two spaces");
116         text = utf8ToUtf16("two  spaces");
117         Range range(0, text.size());
118         Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
119                       EndHyphenEdit::NO_EDIT);
120         EXPECT_EQ(110.0f, layout.getAdvance());
121         layout.getBounds(&rect);
122         EXPECT_EQ(0.0f, rect.mLeft);
123         EXPECT_EQ(10.0f, rect.mTop);
124         EXPECT_EQ(110.0f, rect.mRight);
125         EXPECT_EQ(0.0f, rect.mBottom);
126         expectedValues.resize(text.size());
127         for (size_t i = 0; i < expectedValues.size(); ++i) {
128             expectedValues[i] = 10.0f;
129         }
130         expectAdvances(expectedValues, layout.getAdvances());
131     }
132 }
133 
TEST_F(LayoutTest,doLayoutTest_wordSpacing)134 TEST_F(LayoutTest, doLayoutTest_wordSpacing) {
135     MinikinPaint paint(mCollection);
136     paint.size = 10.0f;  // make 1em = 10px
137     MinikinRect rect;
138     std::vector<float> expectedValues;
139     std::vector<uint16_t> text;
140 
141     paint.wordSpacing = 5.0f;
142 
143     // The mock implementation returns 10.0f advance and 0,0-10x10 bounds for all glyph.
144     {
145         SCOPED_TRACE("one word");
146         text = utf8ToUtf16("oneword");
147         Range range(0, text.size());
148         Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
149                       EndHyphenEdit::NO_EDIT);
150         EXPECT_EQ(70.0f, layout.getAdvance());
151         layout.getBounds(&rect);
152         EXPECT_EQ(0.0f, rect.mLeft);
153         EXPECT_EQ(10.0f, rect.mTop);
154         EXPECT_EQ(70.0f, rect.mRight);
155         EXPECT_EQ(0.0f, rect.mBottom);
156         expectedValues.resize(text.size());
157         for (size_t i = 0; i < expectedValues.size(); ++i) {
158             expectedValues[i] = 10.0f;
159         }
160         expectAdvances(expectedValues, layout.getAdvances());
161     }
162     {
163         SCOPED_TRACE("two words");
164         text = utf8ToUtf16("two words");
165         Range range(0, text.size());
166         Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
167                       EndHyphenEdit::NO_EDIT);
168         EXPECT_EQ(95.0f, layout.getAdvance());
169         layout.getBounds(&rect);
170         EXPECT_EQ(0.0f, rect.mLeft);
171         EXPECT_EQ(10.0f, rect.mTop);
172         EXPECT_EQ(95.0f, rect.mRight);
173         EXPECT_EQ(0.0f, rect.mBottom);
174         expectedValues.resize(text.size());
175         for (size_t i = 0; i < expectedValues.size(); ++i) {
176             expectedValues[i] = 10.0f;
177         }
178         expectedValues[3] = 15.0f;
179         expectAdvances(expectedValues, layout.getAdvances());
180     }
181     {
182         SCOPED_TRACE("three words test");
183         text = utf8ToUtf16("three words test");
184         Range range(0, text.size());
185         Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
186                       EndHyphenEdit::NO_EDIT);
187         EXPECT_EQ(170.0f, layout.getAdvance());
188         layout.getBounds(&rect);
189         EXPECT_EQ(0.0f, rect.mLeft);
190         EXPECT_EQ(10.0f, rect.mTop);
191         EXPECT_EQ(170.0f, rect.mRight);
192         EXPECT_EQ(0.0f, rect.mBottom);
193         expectedValues.resize(text.size());
194         for (size_t i = 0; i < expectedValues.size(); ++i) {
195             expectedValues[i] = 10.0f;
196         }
197         expectedValues[5] = 15.0f;
198         expectedValues[11] = 15.0f;
199         expectAdvances(expectedValues, layout.getAdvances());
200     }
201     {
202         SCOPED_TRACE("two spaces");
203         text = utf8ToUtf16("two  spaces");
204         Range range(0, text.size());
205         Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
206                       EndHyphenEdit::NO_EDIT);
207         EXPECT_EQ(120.0f, layout.getAdvance());
208         layout.getBounds(&rect);
209         EXPECT_EQ(0.0f, rect.mLeft);
210         EXPECT_EQ(10.0f, rect.mTop);
211         EXPECT_EQ(120.0f, rect.mRight);
212         EXPECT_EQ(0.0f, rect.mBottom);
213         expectedValues.resize(text.size());
214         for (size_t i = 0; i < expectedValues.size(); ++i) {
215             expectedValues[i] = 10.0f;
216         }
217         expectedValues[3] = 15.0f;
218         expectedValues[4] = 15.0f;
219         expectAdvances(expectedValues, layout.getAdvances());
220     }
221 }
222 
TEST_F(LayoutTest,doLayoutTest_negativeWordSpacing)223 TEST_F(LayoutTest, doLayoutTest_negativeWordSpacing) {
224     MinikinPaint paint(mCollection);
225     paint.size = 10.0f;  // make 1em = 10px
226     MinikinRect rect;
227     std::vector<float> expectedValues;
228 
229     std::vector<uint16_t> text;
230 
231     // Negative word spacing also should work.
232     paint.wordSpacing = -5.0f;
233 
234     {
235         SCOPED_TRACE("one word");
236         text = utf8ToUtf16("oneword");
237         Range range(0, text.size());
238         Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
239                       EndHyphenEdit::NO_EDIT);
240         EXPECT_EQ(70.0f, layout.getAdvance());
241         layout.getBounds(&rect);
242         EXPECT_EQ(0.0f, rect.mLeft);
243         EXPECT_EQ(10.0f, rect.mTop);
244         EXPECT_EQ(70.0f, rect.mRight);
245         EXPECT_EQ(0.0f, rect.mBottom);
246         expectedValues.resize(text.size());
247         for (size_t i = 0; i < expectedValues.size(); ++i) {
248             expectedValues[i] = 10.0f;
249         }
250         expectAdvances(expectedValues, layout.getAdvances());
251     }
252     {
253         SCOPED_TRACE("two words");
254         text = utf8ToUtf16("two words");
255         Range range(0, text.size());
256         Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
257                       EndHyphenEdit::NO_EDIT);
258         EXPECT_EQ(85.0f, layout.getAdvance());
259         layout.getBounds(&rect);
260         EXPECT_EQ(0.0f, rect.mLeft);
261         EXPECT_EQ(10.0f, rect.mTop);
262         EXPECT_EQ(85.0f, rect.mRight);
263         EXPECT_EQ(0.0f, rect.mBottom);
264         expectedValues.resize(text.size());
265         for (size_t i = 0; i < expectedValues.size(); ++i) {
266             expectedValues[i] = 10.0f;
267         }
268         expectedValues[3] = 5.0f;
269         expectAdvances(expectedValues, layout.getAdvances());
270     }
271     {
272         SCOPED_TRACE("three words");
273         text = utf8ToUtf16("three word test");
274         Range range(0, text.size());
275         Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
276                       EndHyphenEdit::NO_EDIT);
277         EXPECT_EQ(140.0f, layout.getAdvance());
278         layout.getBounds(&rect);
279         EXPECT_EQ(0.0f, rect.mLeft);
280         EXPECT_EQ(10.0f, rect.mTop);
281         EXPECT_EQ(140.0f, rect.mRight);
282         EXPECT_EQ(0.0f, rect.mBottom);
283         expectedValues.resize(text.size());
284         for (size_t i = 0; i < expectedValues.size(); ++i) {
285             expectedValues[i] = 10.0f;
286         }
287         expectedValues[5] = 5.0f;
288         expectedValues[10] = 5.0f;
289         expectAdvances(expectedValues, layout.getAdvances());
290     }
291     {
292         SCOPED_TRACE("two spaces");
293         text = utf8ToUtf16("two  spaces");
294         Range range(0, text.size());
295         Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
296                       EndHyphenEdit::NO_EDIT);
297         EXPECT_EQ(100.0f, layout.getAdvance());
298         layout.getBounds(&rect);
299         EXPECT_EQ(0.0f, rect.mLeft);
300         EXPECT_EQ(10.0f, rect.mTop);
301         EXPECT_EQ(100.0f, rect.mRight);
302         EXPECT_EQ(0.0f, rect.mBottom);
303         expectedValues.resize(text.size());
304         for (size_t i = 0; i < expectedValues.size(); ++i) {
305             expectedValues[i] = 10.0f;
306         }
307         expectedValues[3] = 5.0f;
308         expectedValues[4] = 5.0f;
309         expectAdvances(expectedValues, layout.getAdvances());
310     }
311 }
312 
313 // Test that a forced-RTL layout correctly mirros a forced-LTR layout.
TEST_F(LayoutTest,doLayoutTest_rtlTest)314 TEST_F(LayoutTest, doLayoutTest_rtlTest) {
315     MinikinPaint paint(mCollection);
316 
317     std::vector<uint16_t> text = parseUnicodeString("'a' 'b' U+3042 U+3043 'c' 'd'");
318     Range range(0, text.size());
319 
320     Layout ltrLayout(text, range, Bidi::FORCE_LTR, paint, StartHyphenEdit::NO_EDIT,
321                      EndHyphenEdit::NO_EDIT);
322 
323     Layout rtlLayout(text, range, Bidi::FORCE_RTL, paint, StartHyphenEdit::NO_EDIT,
324                      EndHyphenEdit::NO_EDIT);
325 
326     ASSERT_EQ(ltrLayout.nGlyphs(), rtlLayout.nGlyphs());
327     ASSERT_EQ(6u, ltrLayout.nGlyphs());
328 
329     size_t nGlyphs = ltrLayout.nGlyphs();
330     for (size_t i = 0; i < nGlyphs; ++i) {
331         EXPECT_EQ(ltrLayout.getFont(i), rtlLayout.getFont(nGlyphs - i - 1));
332         EXPECT_EQ(ltrLayout.getGlyphId(i), rtlLayout.getGlyphId(nGlyphs - i - 1));
333     }
334 }
335 
336 // Test that single-run RTL layouts of LTR-only text is laid out identical to an LTR layout.
TEST_F(LayoutTest,singleRunBidiTest)337 TEST_F(LayoutTest, singleRunBidiTest) {
338     MinikinPaint paint(mCollection);
339 
340     std::vector<uint16_t> text = parseUnicodeString("'1' '2' '3'");
341     Range range(0, text.size());
342 
343     Layout ltrLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
344                      EndHyphenEdit::NO_EDIT);
345 
346     Layout rtlLayout(text, range, Bidi::RTL, paint, StartHyphenEdit::NO_EDIT,
347                      EndHyphenEdit::NO_EDIT);
348 
349     Layout defaultRtlLayout(text, range, Bidi::DEFAULT_RTL, paint, StartHyphenEdit::NO_EDIT,
350                             EndHyphenEdit::NO_EDIT);
351 
352     const size_t nGlyphs = ltrLayout.nGlyphs();
353     ASSERT_EQ(3u, nGlyphs);
354 
355     ASSERT_EQ(nGlyphs, rtlLayout.nGlyphs());
356     ASSERT_EQ(nGlyphs, defaultRtlLayout.nGlyphs());
357 
358     for (size_t i = 0; i < nGlyphs; ++i) {
359         EXPECT_EQ(ltrLayout.getFont(i), rtlLayout.getFont(i));
360         EXPECT_EQ(ltrLayout.getGlyphId(i), rtlLayout.getGlyphId(i));
361         EXPECT_EQ(ltrLayout.getFont(i), defaultRtlLayout.getFont(i));
362         EXPECT_EQ(ltrLayout.getGlyphId(i), defaultRtlLayout.getGlyphId(i));
363     }
364 }
365 
TEST_F(LayoutTest,hyphenationTest)366 TEST_F(LayoutTest, hyphenationTest) {
367     MinikinPaint paint(mCollection);
368     paint.size = 10.0f;  // make 1em = 10px
369     std::vector<uint16_t> text;
370 
371     // The mock implementation returns 10.0f advance for all glyphs.
372     {
373         SCOPED_TRACE("one word with no hyphen edit");
374         text = utf8ToUtf16("oneword");
375         Range range(0, text.size());
376         Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
377                       EndHyphenEdit::NO_EDIT);
378         EXPECT_EQ(70.0f, layout.getAdvance());
379     }
380     {
381         SCOPED_TRACE("one word with hyphen insertion at the end");
382         text = utf8ToUtf16("oneword");
383         Range range(0, text.size());
384         Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
385                       EndHyphenEdit::INSERT_HYPHEN);
386         EXPECT_EQ(80.0f, layout.getAdvance());
387     }
388     {
389         SCOPED_TRACE("one word with hyphen replacement at the end");
390         text = utf8ToUtf16("oneword");
391         Range range(0, text.size());
392         Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
393                       EndHyphenEdit::REPLACE_WITH_HYPHEN);
394         EXPECT_EQ(70.0f, layout.getAdvance());
395     }
396     {
397         SCOPED_TRACE("one word with hyphen insertion at the start");
398         text = utf8ToUtf16("oneword");
399         Range range(0, text.size());
400         Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::INSERT_HYPHEN,
401                       EndHyphenEdit::NO_EDIT);
402         EXPECT_EQ(80.0f, layout.getAdvance());
403     }
404     {
405         SCOPED_TRACE("one word with hyphen insertion at the both ends");
406         text = utf8ToUtf16("oneword");
407         Range range(0, text.size());
408         Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::INSERT_HYPHEN,
409                       EndHyphenEdit::INSERT_HYPHEN);
410         EXPECT_EQ(90.0f, layout.getAdvance());
411     }
412 }
413 
TEST_F(LayoutTest,measuredTextTest)414 TEST_F(LayoutTest, measuredTextTest) {
415     // The test font has following coverage and width.
416     // U+0020: 10em
417     // U+002E (.): 10em
418     // U+0043 (C): 100em
419     // U+0049 (I): 1em
420     // U+004C (L): 50em
421     // U+0056 (V): 5em
422     // U+0058 (X): 10em
423     // U+005F (_): 0em
424     // U+FFFD (invalid surrogate will be replaced to this): 7em
425     // U+10331 (\uD800\uDF31): 10em
426     auto fc = buildFontCollection("LayoutTestFont.ttf");
427     {
428         MinikinPaint paint(fc);
429         std::vector<uint16_t> text = utf8ToUtf16("I");
430         std::vector<float> advances(text.size());
431         Range range(0, text.size());
432         EXPECT_EQ(1.0f, Layout::measureText(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
433                                             EndHyphenEdit::NO_EDIT, advances.data()));
434         ASSERT_EQ(1u, advances.size());
435         EXPECT_EQ(1.0f, advances[0]);
436     }
437     {
438         MinikinPaint paint(fc);
439         std::vector<uint16_t> text = utf8ToUtf16("IV");
440         std::vector<float> advances(text.size());
441         Range range(0, text.size());
442         EXPECT_EQ(6.0f, Layout::measureText(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
443                                             EndHyphenEdit::NO_EDIT, advances.data()));
444         ASSERT_EQ(2u, advances.size());
445         EXPECT_EQ(1.0f, advances[0]);
446         EXPECT_EQ(5.0f, advances[1]);
447     }
448     {
449         MinikinPaint paint(fc);
450         std::vector<uint16_t> text = utf8ToUtf16("IVX");
451         std::vector<float> advances(text.size());
452         Range range(0, text.size());
453         EXPECT_EQ(16.0f,
454                   Layout::measureText(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
455                                       EndHyphenEdit::NO_EDIT, advances.data()));
456         ASSERT_EQ(3u, advances.size());
457         EXPECT_EQ(1.0f, advances[0]);
458         EXPECT_EQ(5.0f, advances[1]);
459         EXPECT_EQ(10.0f, advances[2]);
460     }
461 }
462 
463 // TODO: Add more test cases, e.g. measure text, letter spacing.
464 
465 }  // namespace minikin
466