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