• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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