1 /* 2 * Copyright (C) 2019 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 package android.text.cts 17 18 import android.content.Context 19 import android.graphics.Typeface 20 import android.graphics.text.LineBreakConfig 21 import android.graphics.text.LineBreaker 22 import android.platform.test.flag.junit.CheckFlagsRule 23 import android.platform.test.flag.junit.DeviceFlagsValueProvider 24 import android.text.StaticLayout 25 import android.text.TextPaint 26 import androidx.test.filters.SmallTest 27 import androidx.test.platform.app.InstrumentationRegistry 28 import androidx.test.runner.AndroidJUnit4 29 import java.util.Locale 30 import org.junit.Assert 31 import org.junit.Rule 32 import org.junit.Test 33 import org.junit.runner.RunWith 34 35 @SmallTest 36 @RunWith(AndroidJUnit4::class) 37 class StaticLayoutLineBreakAutoTest { 38 @Rule 39 @JvmField 40 val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() 41 42 companion object { 43 // A test string for Japanese. The meaning is that "Today is a sunny day." 44 // The expected line break of phrase and non-phrase cases are: 45 // \u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002 46 // Phrase: ^ ^ ^ 47 // Non-Phrase: ^ ^ ^ ^ ^ ^ ^ ^ ^ 48 private val JP_TEXT = "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002" 49 50 // A test string for Korean. The meaning is that "I want to eat breakfast." 51 // The phrase based line break breaks at spaces, non-phrase based line break breaks at 52 // grapheme. 53 private val KO_TEXT = "\uC544\uCE68\uBC25\uC744\u0020\uBA39\uACE0\u0020" + 54 "\uC2F6\uC2B5\uB2C8\uB2E4\u002E" 55 56 // A test string for Japanese. The meaning is a name of snack. 57 // \u30B5\u30FC\u30BF\u30FC\u30A2\u30F3\u30C0\u30AE\u30FC 58 // Normal: ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ 59 // Strict: ^ ^ ^ ^ ^ ^ ^ 60 private val JP_TEXT_FOR_STRICT = "\u30B5\u30FC\u30BF\u30FC\u30A2\u30F3\u30C0\u30AE\u30FC" 61 62 private val JP_FONT = "fonts/Hiragana.ttf" 63 private val KO_FONT = "fonts/Hangul.ttf" 64 65 private enum class Language { Japanese, Korean } 66 setupPaintnull67 private fun setupPaint(lang: Language): TextPaint { 68 val context: Context = InstrumentationRegistry.getInstrumentation().getTargetContext() 69 val paint = TextPaint() 70 71 if (lang == Language.Japanese) { 72 paint.typeface = Typeface.createFromAsset(context.assets, JP_FONT) 73 paint.textLocale = Locale.JAPANESE 74 } else { 75 paint.typeface = Typeface.createFromAsset(context.assets, KO_FONT) 76 paint.textLocale = Locale.KOREAN 77 } 78 paint.textSize = 10.0f // Make 1em == 10px. 79 return paint 80 } 81 buildLayoutnull82 private fun buildLayout( 83 text: String, 84 lang: Language, 85 lineBreakConfig: LineBreakConfig, 86 width: Int 87 ): StaticLayout { 88 val builder = StaticLayout.Builder.obtain( 89 text, 90 0, 91 text.length, 92 setupPaint(lang), 93 width 94 ) 95 builder.setBreakStrategy(LineBreaker.BREAK_STRATEGY_HIGH_QUALITY) 96 builder.setLineBreakConfig(lineBreakConfig) 97 return builder.build() 98 } 99 assertLineBreaknull100 private fun assertLineBreak( 101 text: String, 102 lang: Language, 103 lineBreakConfig: LineBreakConfig, 104 width: Int, 105 vararg expected: String 106 ) { 107 val layout = buildLayout(text, lang, lineBreakConfig, width) 108 Assert.assertEquals(expected.size.toLong(), layout.lineCount.toLong()) 109 var currentExpectedOffset = 0 110 for (i in expected.indices) { 111 currentExpectedOffset += expected[i].length 112 val msg = (i.toString() + "th line should be " + expected[i] + ", but it was " + 113 text.substring(layout.getLineStart(i), layout.getLineEnd(i))) 114 Assert.assertEquals( 115 msg, 116 currentExpectedOffset.toLong(), 117 layout.getLineEnd(i).toLong() 118 ) 119 } 120 } 121 } 122 123 @Test testAuto_JapaneseNonenull124 fun testAuto_JapaneseNone() { 125 val config = LineBreakConfig.Builder() 126 .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE) 127 .build() 128 129 assertLineBreak( 130 JP_TEXT.repeat(1), 131 Language.Japanese, 132 config, 133 100, 134 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002" 135 ) 136 137 assertLineBreak( 138 JP_TEXT.repeat(2), 139 Language.Japanese, 140 config, 141 100, 142 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002\u672C\u65E5", 143 "\u306F\u6674\u5929\u306A\u308A\u3002" 144 ) 145 146 assertLineBreak( 147 JP_TEXT.repeat(3), 148 Language.Japanese, 149 config, 150 100, 151 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002\u672C\u65E5", 152 "\u306F\u6674\u5929\u306A\u308A\u3002\u672C\u65E5\u306F\u6674", 153 "\u5929\u306A\u308A\u3002" 154 ) 155 156 assertLineBreak( 157 JP_TEXT.repeat(4), 158 Language.Japanese, 159 config, 160 100, 161 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002\u672C\u65E5", 162 "\u306F\u6674\u5929\u306A\u308A\u3002\u672C\u65E5\u306F\u6674", 163 "\u5929\u306A\u308A\u3002\u672C\u65E5\u306F\u6674\u5929\u306A", 164 "\u308A\u3002" 165 ) 166 167 assertLineBreak( 168 JP_TEXT.repeat(5), 169 Language.Japanese, 170 config, 171 100, 172 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002\u672C\u65E5", 173 "\u306F\u6674\u5929\u306A\u308A\u3002\u672C\u65E5\u306F\u6674", 174 "\u5929\u306A\u308A\u3002\u672C\u65E5\u306F\u6674\u5929\u306A", 175 "\u308A\u3002\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002" 176 ) 177 178 assertLineBreak(JP_TEXT.repeat(6), Language.Japanese, config, 100, 179 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002\u672C\u65E5", 180 "\u306F\u6674\u5929\u306A\u308A\u3002\u672C\u65E5\u306F\u6674", 181 "\u5929\u306A\u308A\u3002\u672C\u65E5\u306F\u6674\u5929\u306A", 182 "\u308A\u3002\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 183 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002" 184 ) 185 } 186 187 @Test testAuto_JapanesePhrasenull188 fun testAuto_JapanesePhrase() { 189 val config = LineBreakConfig.Builder() 190 .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) 191 .build() 192 193 assertLineBreak( 194 JP_TEXT.repeat(1), 195 Language.Japanese, 196 config, 197 100, 198 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002" 199 ) 200 201 assertLineBreak( 202 JP_TEXT.repeat(2), 203 Language.Japanese, 204 config, 205 100, 206 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 207 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002" 208 ) 209 210 assertLineBreak( 211 JP_TEXT.repeat(3), 212 Language.Japanese, 213 config, 214 100, 215 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 216 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 217 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002" 218 ) 219 220 assertLineBreak( 221 JP_TEXT.repeat(4), 222 Language.Japanese, 223 config, 224 100, 225 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 226 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 227 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 228 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002" 229 ) 230 231 // instead. 232 assertLineBreak( 233 JP_TEXT.repeat(5), 234 Language.Japanese, 235 config, 236 100, 237 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 238 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 239 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 240 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 241 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002" 242 ) 243 244 assertLineBreak( 245 JP_TEXT.repeat(6), 246 Language.Japanese, 247 config, 248 100, 249 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 250 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 251 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 252 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 253 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 254 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002" 255 ) 256 } 257 258 @Test testAuto_JapaneseAutonull259 fun testAuto_JapaneseAuto() { 260 val config = LineBreakConfig.Builder() 261 .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO) 262 .build() 263 264 assertLineBreak( 265 JP_TEXT.repeat(1), 266 Language.Japanese, 267 config, 268 100, 269 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002" 270 ) 271 272 assertLineBreak( 273 JP_TEXT.repeat(2), 274 Language.Japanese, 275 config, 276 100, 277 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 278 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002" 279 ) 280 281 assertLineBreak( 282 JP_TEXT.repeat(3), 283 Language.Japanese, 284 config, 285 100, 286 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 287 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 288 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002" 289 ) 290 291 assertLineBreak( 292 JP_TEXT.repeat(4), 293 Language.Japanese, 294 config, 295 100, 296 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 297 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 298 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 299 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002" 300 ) 301 302 // When the line count becomes 5 or more as a result of phrase break, use non-phrase break 303 // instead. 304 assertLineBreak( 305 JP_TEXT.repeat(5), 306 Language.Japanese, 307 config, 308 100, 309 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002\u672C\u65E5", 310 "\u306F\u6674\u5929\u306A\u308A\u3002\u672C\u65E5\u306F\u6674", 311 "\u5929\u306A\u308A\u3002\u672C\u65E5\u306F\u6674\u5929\u306A", 312 "\u308A\u3002\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002" 313 ) 314 315 assertLineBreak( 316 JP_TEXT.repeat(6), 317 Language.Japanese, 318 config, 319 100, 320 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002\u672C\u65E5", 321 "\u306F\u6674\u5929\u306A\u308A\u3002\u672C\u65E5\u306F\u6674", 322 "\u5929\u306A\u308A\u3002\u672C\u65E5\u306F\u6674\u5929\u306A", 323 "\u308A\u3002\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002", 324 "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002" 325 ) 326 } 327 328 @Test testAuto_JapaneseStyleAutoStrictnull329 fun testAuto_JapaneseStyleAutoStrict() { 330 val normal = LineBreakConfig.Builder() 331 .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_NORMAL) 332 .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE) 333 .build() 334 335 val strict = LineBreakConfig.Builder() 336 .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT) 337 .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE) 338 .build() 339 340 val auto = LineBreakConfig.Builder() 341 .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_AUTO) 342 .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE) 343 .build() 344 345 assertLineBreak( 346 JP_TEXT_FOR_STRICT, 347 Language.Japanese, 348 normal, 349 80, 350 "\u30B5\u30FC\u30BF\u30FC\u30A2\u30F3\u30C0\u30AE", 351 "\u30FC" 352 ) 353 354 assertLineBreak( 355 JP_TEXT_FOR_STRICT, 356 Language.Japanese, 357 strict, 358 80, 359 "\u30B5\u30FC\u30BF\u30FC\u30A2\u30F3\u30C0", 360 "\u30AE\u30FC" 361 ) 362 363 assertLineBreak( 364 JP_TEXT_FOR_STRICT, 365 Language.Japanese, 366 auto, 367 80, 368 "\u30B5\u30FC\u30BF\u30FC\u30A2\u30F3\u30C0", 369 "\u30AE\u30FC" 370 ) 371 } 372 373 @Test testAuto_Koreannull374 fun testAuto_Korean() { 375 val none = LineBreakConfig.Builder() 376 .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE) 377 .build() 378 379 val phrase = LineBreakConfig.Builder() 380 .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) 381 .build() 382 383 val auto = LineBreakConfig.Builder() 384 .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO) 385 .build() 386 387 // LINE_BREAK_WORD_STYLE_NONE should break at grapheme 388 assertLineBreak( 389 KO_TEXT, 390 Language.Korean, 391 none, 392 100, 393 "\uC544\uCE68\uBC25\uC744\u0020\uBA39\uACE0\u0020\uC2F6\uC2B5", 394 "\uB2C8\uB2E4\u002E" 395 ) 396 397 // LINE_BREAK_WORD_STYLE_PHRASE should break at whitespace 398 assertLineBreak( 399 KO_TEXT, 400 Language.Korean, 401 phrase, 402 100, 403 "\uC544\uCE68\uBC25\uC744\u0020\uBA39\uACE0\u0020", 404 "\uC2F6\uC2B5\uB2C8\uB2E4\u002E" 405 ) 406 407 // LINE_BREAK_WORD_STYLE_AUTO should work as PHRASE. 408 assertLineBreak( 409 KO_TEXT, 410 Language.Korean, 411 auto, 412 100, 413 "\uC544\uCE68\uBC25\uC744\u0020\uBA39\uACE0\u0020", 414 "\uC2F6\uC2B5\uB2C8\uB2E4\u002E" 415 ) 416 } 417 } 418