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 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: 40 LayoutTest() : mCollection(nullptr) {} 41 42 virtual ~LayoutTest() {} 43 44 virtual void SetUp() override { mCollection = buildFontCollection("Ascii.ttf"); } 45 46 virtual void TearDown() override {} 47 48 std::shared_ptr<FontCollection> mCollection; 49 }; 50 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 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 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. 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. 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 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 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