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