[go: nahoru, domu]

blob: 5c942c679025bd67d777de92cd7e8a7dc89df5d2 [file] [log] [blame]
Haoyu Zhang10fc6502018-11-09 15:50:11 -08001package androidx.ui.port.engine.text.platform
2
Haoyu Zhang10fc6502018-11-09 15:50:11 -08003import android.graphics.Paint
Haoyu Zhang10fc6502018-11-09 15:50:11 -08004import android.text.TextPaint
Haoyu Zhangbb51ccf2018-12-17 15:07:43 -08005import android.text.style.AbsoluteSizeSpan
Haoyu Zhang0c4fd022019-01-02 13:53:10 -08006import android.text.style.BackgroundColorSpan
Haoyu Zhang47f57a22018-12-12 13:16:40 -08007import android.text.style.ForegroundColorSpan
Haoyu Zhang4b201d22018-12-18 14:27:38 -08008import android.text.style.LocaleSpan
Haoyu Zhang15750922019-02-19 13:44:17 -08009import android.text.style.ScaleXSpan
Haoyu Zhangb5677202018-12-19 14:22:18 -080010import android.text.style.StrikethroughSpan
11import android.text.style.UnderlineSpan
Haoyu Zhang97d5e722019-01-04 17:32:34 -080012import androidx.test.filters.SdkSuppress
Haoyu Zhang10fc6502018-11-09 15:50:11 -080013import androidx.test.filters.SmallTest
14import androidx.test.platform.app.InstrumentationRegistry
Haoyu Zhang2eb38cd2018-11-21 15:05:28 -080015import androidx.text.StaticLayoutCompat
Haoyu Zhang40ce6bf2019-01-28 15:58:20 -080016import androidx.text.style.BaselineShiftSpan
Haoyu Zhang1588ba0f2018-12-19 16:47:10 -080017import androidx.text.style.LetterSpacingSpan
Haoyu Zhang15750922019-02-19 13:44:17 -080018import androidx.text.style.SkewXSpan
Siyamed Sinir70f336a2018-12-27 10:37:09 -080019import androidx.text.style.TypefaceSpan
Haoyu Zhang97d5e722019-01-04 17:32:34 -080020import androidx.text.style.WordSpacingSpan
Haoyu Zhang40ce6bf2019-01-28 15:58:20 -080021import androidx.ui.engine.text.BaselineShift
Siyamed Sinirf18dcca2018-12-20 18:25:10 -080022import androidx.ui.engine.text.FontStyle
Siyamed Sinir21796012018-12-26 13:51:17 -080023import androidx.ui.engine.text.FontSynthesis
Siyamed Sinirf18dcca2018-12-20 18:25:10 -080024import androidx.ui.engine.text.FontWeight
Haoyu Zhang47f57a22018-12-12 13:16:40 -080025import androidx.ui.engine.text.ParagraphBuilder
Haoyu Zhang10fc6502018-11-09 15:50:11 -080026import androidx.ui.engine.text.ParagraphStyle
27import androidx.ui.engine.text.TextAlign
Haoyu Zhangb5677202018-12-19 14:22:18 -080028import androidx.ui.engine.text.TextDecoration
Haoyu Zhang47f57a22018-12-12 13:16:40 -080029import androidx.ui.engine.text.TextStyle
Siyamed Sinirf18dcca2018-12-20 18:25:10 -080030import androidx.ui.engine.text.font.FontFamily
31import androidx.ui.engine.text.font.asFontFamily
Haoyu Zhang10fc6502018-11-09 15:50:11 -080032import androidx.ui.engine.text.platform.ParagraphAndroid
Siyamed Sinirf18dcca2018-12-20 18:25:10 -080033import androidx.ui.engine.text.platform.TypefaceAdapter
Haoyu Zhang4b201d22018-12-18 14:27:38 -080034import androidx.ui.engine.window.Locale
Haoyu Zhang47f57a22018-12-12 13:16:40 -080035import androidx.ui.painting.Color
Siyamed Sinir00aa7172019-02-01 13:15:56 -080036import androidx.ui.engine.text.FontTestData.Companion.BASIC_MEASURE_FONT
Haoyu Zhang15750922019-02-19 13:44:17 -080037import androidx.ui.engine.text.TextGeometricTransform
Siyamed Sinir00aa7172019-02-01 13:15:56 -080038import androidx.ui.matchers.equalToBitmap
39import androidx.ui.matchers.hasSpan
40import androidx.ui.matchers.hasSpanOnTop
Siyamed Sinirf18dcca2018-12-20 18:25:10 -080041import com.nhaarman.mockitokotlin2.any
42import com.nhaarman.mockitokotlin2.eq
43import com.nhaarman.mockitokotlin2.mock
44import com.nhaarman.mockitokotlin2.never
45import com.nhaarman.mockitokotlin2.spy
46import com.nhaarman.mockitokotlin2.times
47import com.nhaarman.mockitokotlin2.verify
Haoyu Zhangb5677202018-12-19 14:22:18 -080048import org.hamcrest.Matchers.equalTo
Siyamed Sinirf18dcca2018-12-20 18:25:10 -080049import org.hamcrest.Matchers.not
50import org.hamcrest.Matchers.nullValue
Haoyu Zhangc11ef5a2019-01-14 16:35:05 -080051import org.junit.Assert.assertFalse
Haoyu Zhang47f57a22018-12-12 13:16:40 -080052import org.junit.Assert.assertThat
Haoyu Zhangc11ef5a2019-01-14 16:35:05 -080053import org.junit.Assert.assertTrue
Haoyu Zhang10fc6502018-11-09 15:50:11 -080054import org.junit.Before
55import org.junit.Test
56import org.junit.runner.RunWith
57import org.junit.runners.JUnit4
Haoyu Zhang10fc6502018-11-09 15:50:11 -080058import kotlin.math.ceil
59
60@RunWith(JUnit4::class)
61@SmallTest
62class ParagraphAndroidTest {
Siyamed Sinirf18dcca2018-12-20 18:25:10 -080063 private lateinit var fontFamily: FontFamily
Haoyu Zhang10fc6502018-11-09 15:50:11 -080064
65 @Before
66 fun setup() {
Haoyu Zhang47f57a22018-12-12 13:16:40 -080067 // This sample font provides the following features:
68 // 1. The width of most of visible characters equals to font size.
69 // 2. The LTR/RTL characters are rendered as â–¶/â—€.
70 // 3. The fontMetrics passed to TextPaint has descend - ascend equal to 1.2 * fontSize.
Siyamed Sinirf18dcca2018-12-20 18:25:10 -080071 fontFamily = BASIC_MEASURE_FONT.asFontFamily()
72 fontFamily.context = InstrumentationRegistry.getInstrumentation().context
Haoyu Zhang10fc6502018-11-09 15:50:11 -080073 }
74
75 @Test
76 fun draw_with_newline_and_line_break_default_values() {
George Mountb1cdd862018-12-21 10:54:42 -080077 val fontSize = 50.0f
Haoyu Zhang10fc6502018-11-09 15:50:11 -080078 for (text in arrayOf("abc\ndef", "\u05D0\u05D1\u05D2\n\u05D3\u05D4\u05D5")) {
79 val paragraphAndroid = simpleParagraph(
80 text = StringBuilder(text),
Siyamed Sinirf18dcca2018-12-20 18:25:10 -080081 fontSize = fontSize,
82 fontFamily = fontFamily
Haoyu Zhang10fc6502018-11-09 15:50:11 -080083 )
84
85 // 2 chars width
86 paragraphAndroid.layout(width = 2 * fontSize)
87
88 val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
George Mountb1cdd862018-12-21 10:54:42 -080089 textPaint.textSize = fontSize
Siyamed Sinirf18dcca2018-12-20 18:25:10 -080090 textPaint.typeface = TypefaceAdapter().create(fontFamily)
Haoyu Zhang10fc6502018-11-09 15:50:11 -080091
Haoyu Zhang2eb38cd2018-11-21 15:05:28 -080092 val staticLayout = StaticLayoutCompat.Builder(
93 text,
94 textPaint,
95 ceil(paragraphAndroid.width).toInt()
Haoyu Zhang10fc6502018-11-09 15:50:11 -080096 )
Haoyu Zhang2eb38cd2018-11-21 15:05:28 -080097 .setEllipsizedWidth(ceil(paragraphAndroid.width).toInt())
98 .build()
Haoyu Zhang47f57a22018-12-12 13:16:40 -080099 assertThat(paragraphAndroid.bitmap(), equalToBitmap(staticLayout.bitmap()))
Haoyu Zhang10fc6502018-11-09 15:50:11 -0800100 }
101 }
102
Haoyu Zhang47f57a22018-12-12 13:16:40 -0800103 @Test
104 fun textStyle_setColorOnWholeText() {
105 val text = "abcde"
Haoyu Zhang47f57a22018-12-12 13:16:40 -0800106 val textStyle = TextStyle(color = Color(0xFF0000FF.toInt()))
107
108 val paragraph = simpleParagraph(
109 text = text,
Haoyu Zhang4b201d22018-12-18 14:27:38 -0800110 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
Haoyu Zhang47f57a22018-12-12 13:16:40 -0800111 )
George Mountb1cdd862018-12-21 10:54:42 -0800112 paragraph.layout(100.0f)
Haoyu Zhang47f57a22018-12-12 13:16:40 -0800113
114 assertThat(paragraph.underlyingText, hasSpan(ForegroundColorSpan::class, 0, text.length))
115 }
116
117 @Test
118 fun textStyle_setColorOnPartOfText() {
119 val text = "abcde"
Haoyu Zhang47f57a22018-12-12 13:16:40 -0800120 val textStyle = TextStyle(color = Color(0xFF0000FF.toInt()))
121
122 val paragraph = simpleParagraph(
123 text = text,
Haoyu Zhang4b201d22018-12-18 14:27:38 -0800124 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length))
Haoyu Zhang47f57a22018-12-12 13:16:40 -0800125 )
George Mountb1cdd862018-12-21 10:54:42 -0800126 paragraph.layout(100.0f)
Haoyu Zhang47f57a22018-12-12 13:16:40 -0800127
128 assertThat(paragraph.underlyingText, hasSpan(ForegroundColorSpan::class, 0, "abc".length))
129 }
130
131 @Test
132 fun textStyle_setColorTwice_lastOneOverwrite() {
133 val text = "abcde"
Haoyu Zhang47f57a22018-12-12 13:16:40 -0800134 val textStyle = TextStyle(color = Color(0xFF0000FF.toInt()))
135 val textStyleOverwrite = TextStyle(color = Color(0xFF00FF00.toInt()))
136
137 val paragraph = simpleParagraph(
138 text = text,
139 textStyles = listOf(
140 ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length),
141 ParagraphBuilder.TextStyleIndex(textStyleOverwrite, 0, "abc".length)
Haoyu Zhang4b201d22018-12-18 14:27:38 -0800142 )
Haoyu Zhang47f57a22018-12-12 13:16:40 -0800143 )
George Mountb1cdd862018-12-21 10:54:42 -0800144 paragraph.layout(100.0f)
Haoyu Zhang47f57a22018-12-12 13:16:40 -0800145
146 assertThat(paragraph.underlyingText, hasSpan(ForegroundColorSpan::class, 0, text.length))
147 assertThat(paragraph.underlyingText, hasSpan(ForegroundColorSpan::class, 0, "abc".length))
148 assertThat(
149 paragraph.underlyingText,
150 hasSpanOnTop(ForegroundColorSpan::class, 0, "abc".length)
151 )
152 }
153
Haoyu Zhangbb51ccf2018-12-17 15:07:43 -0800154 @Test
Haoyu Zhangb5677202018-12-19 14:22:18 -0800155 fun testStyle_setTextDecorationOnWholeText_withLineThrough() {
156 val text = "abcde"
157 val textStyle = TextStyle(decoration = TextDecoration.lineThrough)
158
159 val paragraph = simpleParagraph(
160 text = text,
161 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
162 )
George Mountb1cdd862018-12-21 10:54:42 -0800163 paragraph.layout(100.0f)
Haoyu Zhangb5677202018-12-19 14:22:18 -0800164
165 assertThat(paragraph.underlyingText.toString(), equalTo(text))
166 assertThat(paragraph.underlyingText, hasSpan(StrikethroughSpan::class, 0, text.length))
167 }
168
169 @Test
170 fun testStyle_setTextDecorationOnWholeText_withUnderline() {
171 val text = "abcde"
172 val textStyle = TextStyle(decoration = TextDecoration.underline)
173
174 val paragraph = simpleParagraph(
175 text = text,
176 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
177 )
George Mountb1cdd862018-12-21 10:54:42 -0800178 paragraph.layout(100.0f)
Haoyu Zhangb5677202018-12-19 14:22:18 -0800179
180 assertThat(paragraph.underlyingText.toString(), equalTo(text))
181 assertThat(paragraph.underlyingText, hasSpan(UnderlineSpan::class, 0, text.length))
182 }
183
184 @Test
185 fun testStyle_setTextDecorationOnPartText_withLineThrough() {
186 val text = "abcde"
187 val textStyle = TextStyle(decoration = TextDecoration.lineThrough)
188
189 val paragraph = simpleParagraph(
190 text = text,
191 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length))
192 )
George Mountb1cdd862018-12-21 10:54:42 -0800193 paragraph.layout(100.0f)
Haoyu Zhangb5677202018-12-19 14:22:18 -0800194
195 assertThat(paragraph.underlyingText.toString(), equalTo(text))
196 assertThat(paragraph.underlyingText, hasSpan(StrikethroughSpan::class, 0, "abc".length))
197 }
198
199 @Test
200 fun testStyle_setTextDecorationOnPartText_withUnderline() {
201 val text = "abcde"
202 val textStyle = TextStyle(decoration = TextDecoration.underline)
203
204 val paragraph = simpleParagraph(
205 text = text,
206 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length))
207 )
George Mountb1cdd862018-12-21 10:54:42 -0800208 paragraph.layout(100.0f)
Haoyu Zhangb5677202018-12-19 14:22:18 -0800209
210 assertThat(paragraph.underlyingText.toString(), equalTo(text))
211 assertThat(paragraph.underlyingText, hasSpan(UnderlineSpan::class, 0, "abc".length))
212 }
213
214 @Test
215 fun testStyle_setTextDecoration_withLineThroughAndUnderline() {
216 val text = "abcde"
217 val textStyle = TextStyle(
218 decoration = TextDecoration.combine(
219 listOf(TextDecoration.lineThrough, TextDecoration.underline)
220 )
221 )
222
223 val paragraph = simpleParagraph(
224 text = text,
225 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length))
226 )
George Mountb1cdd862018-12-21 10:54:42 -0800227 paragraph.layout(100.0f)
Haoyu Zhangb5677202018-12-19 14:22:18 -0800228
229 assertThat(paragraph.underlyingText.toString(), equalTo(text))
230 assertThat(paragraph.underlyingText, hasSpan(UnderlineSpan::class, 0, "abc".length))
231 assertThat(paragraph.underlyingText, hasSpan(StrikethroughSpan::class, 0, "abc".length))
232 }
233
234 @Test
Haoyu Zhangbb51ccf2018-12-17 15:07:43 -0800235 fun textStyle_setFontSizeOnWholeText() {
236 val text = "abcde"
George Mountb1cdd862018-12-21 10:54:42 -0800237 val fontSize = 20.0f
Haoyu Zhangbb51ccf2018-12-17 15:07:43 -0800238 val paragraphWidth = text.length * fontSize
239 val textStyle = TextStyle(fontSize = fontSize)
240
241 val paragraph = simpleParagraph(
242 text = text,
243 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
244 )
245 paragraph.layout(paragraphWidth)
246
247 assertThat(paragraph.underlyingText, hasSpan(AbsoluteSizeSpan::class, 0, text.length))
248 }
249
250 @Test
251 fun textStyle_setFontSizeOnPartText() {
252 val text = "abcde"
George Mountb1cdd862018-12-21 10:54:42 -0800253 val fontSize = 20.0f
Haoyu Zhangbb51ccf2018-12-17 15:07:43 -0800254 val paragraphWidth = text.length * fontSize
255 val textStyle = TextStyle(fontSize = fontSize)
256
257 val paragraph = simpleParagraph(
258 text = text,
259 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length))
260 )
261 paragraph.layout(paragraphWidth)
262
263 assertThat(paragraph.underlyingText, hasSpan(AbsoluteSizeSpan::class, 0, "abc".length))
264 }
265
266 @Test
267 fun textStyle_setFontSizeTwice_lastOneOverwrite() {
268 val text = "abcde"
George Mountb1cdd862018-12-21 10:54:42 -0800269 val fontSize = 20.0f
270 val fontSizeOverwrite = 30.0f
Haoyu Zhangbb51ccf2018-12-17 15:07:43 -0800271 val paragraphWidth = text.length * fontSizeOverwrite
272 val textStyle = TextStyle(fontSize = fontSize)
273 val textStyleOverwrite = TextStyle(fontSize = fontSizeOverwrite)
274
275 val paragraph = simpleParagraph(
276 text = text,
277 textStyles = listOf(
278 ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length),
279 ParagraphBuilder.TextStyleIndex(textStyleOverwrite, 0, "abc".length)
280 )
281 )
282 paragraph.layout(paragraphWidth)
283
284 assertThat(paragraph.underlyingText, hasSpan(AbsoluteSizeSpan::class, 0, text.length))
285 assertThat(paragraph.underlyingText, hasSpan(AbsoluteSizeSpan::class, 0, "abc".length))
286 assertThat(
287 paragraph.underlyingText,
288 hasSpanOnTop(AbsoluteSizeSpan::class, 0, "abc".length)
289 )
290 }
291
Haoyu Zhang1588ba0f2018-12-19 16:47:10 -0800292 @Test
293 fun textStyle_setLetterSpacingOnWholeText() {
294 val text = "abcde"
George Mountb1cdd862018-12-21 10:54:42 -0800295 val letterSpacing = 2.0f
Haoyu Zhang1588ba0f2018-12-19 16:47:10 -0800296 val textStyle = TextStyle(letterSpacing = letterSpacing)
297
298 val paragraph = simpleParagraph(
299 text = text,
300 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
301 )
George Mountb1cdd862018-12-21 10:54:42 -0800302 paragraph.layout(100.0f)
Haoyu Zhang1588ba0f2018-12-19 16:47:10 -0800303 assertThat(paragraph.underlyingText.toString(), equalTo(text))
304 assertThat(paragraph.underlyingText, hasSpan(LetterSpacingSpan::class, 0, text.length))
305 }
306
307 @Test
308 fun textStyle_setLetterSpacingOnPartText() {
309 val text = "abcde"
George Mountb1cdd862018-12-21 10:54:42 -0800310 val textStyle = TextStyle(letterSpacing = 2.0f)
Haoyu Zhang1588ba0f2018-12-19 16:47:10 -0800311
312 val paragraph = simpleParagraph(
313 text = text,
314 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length))
315 )
George Mountb1cdd862018-12-21 10:54:42 -0800316 paragraph.layout(100.0f)
Haoyu Zhang1588ba0f2018-12-19 16:47:10 -0800317 assertThat(paragraph.underlyingText.toString(), equalTo(text))
318 assertThat(paragraph.underlyingText, hasSpan(LetterSpacingSpan::class, 0, "abc".length))
319 }
320
321 @Test
Haoyu Zhang4b201d22018-12-18 14:27:38 -0800322 fun textStyle_setLetterSpacingTwice_lastOneOverwrite() {
Haoyu Zhang1588ba0f2018-12-19 16:47:10 -0800323 val text = "abcde"
George Mountb1cdd862018-12-21 10:54:42 -0800324 val textStyle = TextStyle(letterSpacing = 2.0f)
325 val textStyleOverwrite = TextStyle(letterSpacing = 3.0f)
Haoyu Zhang1588ba0f2018-12-19 16:47:10 -0800326
327 val paragraph = simpleParagraph(
328 text = text,
329 textStyles = listOf(
330 ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length),
331 ParagraphBuilder.TextStyleIndex(textStyleOverwrite, 0, "abc".length)
332 )
333 )
George Mountb1cdd862018-12-21 10:54:42 -0800334 paragraph.layout(100.0f)
Haoyu Zhang1588ba0f2018-12-19 16:47:10 -0800335 assertThat(paragraph.underlyingText.toString(), equalTo(text))
336 assertThat(paragraph.underlyingText, hasSpan(LetterSpacingSpan::class, 0, text.length))
337 assertThat(paragraph.underlyingText, hasSpan(LetterSpacingSpan::class, 0, "abc".length))
338 assertThat(
339 paragraph.underlyingText,
340 hasSpanOnTop(LetterSpacingSpan::class, 0, "abc".length)
341 )
342 }
343
Siyamed Sinir70f336a2018-12-27 10:37:09 -0800344 @Test
Haoyu Zhang97d5e722019-01-04 17:32:34 -0800345 @SdkSuppress(minSdkVersion = 28)
346 fun textStyle_setWordSpacingOnWholeText() {
347 val text = "ab cd"
348 val wordSpacing = 2.0f
349 val textStyle = TextStyle(wordSpacing = wordSpacing)
350
351 val paragraph = simpleParagraph(
352 text = text,
353 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
354 )
355 // Notice that the width doesn't matter for this test.
356 paragraph.layout(100.0f)
357
358 assertThat(paragraph.underlyingText.toString(), equalTo(text))
359 assertThat(
360 paragraph.underlyingText,
361 hasSpan(WordSpacingSpan::class, 0, text.length) { span ->
362 span.wordSpacing == wordSpacing.toFloat()
363 }
364 )
365 }
366
367 @Test
368 @SdkSuppress(minSdkVersion = 28)
369 fun textStyle_setWordSpacingOnPartText() {
370 val text = "abc d"
371 val wordSpacing = 2.0f
372 val textStyle = TextStyle(wordSpacing = wordSpacing)
373
374 val paragraph = simpleParagraph(
375 text = text,
376 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length))
377 )
378 // Notice that the width doesn't matter for this test.
379 paragraph.layout(100.0f)
380
381 assertThat(paragraph.underlyingText.toString(), equalTo(text))
382 assertThat(
383 paragraph.underlyingText,
384 hasSpan(WordSpacingSpan::class, 0, "abc".length) { span ->
385 span.wordSpacing == wordSpacing.toFloat()
386 }
387 )
388 }
389
390 @Test
391 @SdkSuppress(minSdkVersion = 28)
392 fun textStyle_setWordSpacingTwice_lastOneOverwrite() {
393 val text = "abc d"
394 val wordSpacing = 2.0f
395 val textStyle = TextStyle(wordSpacing = wordSpacing)
396 val wordSpacingOverwrite = 3.0f
397 val textStyleOverwrite = TextStyle(wordSpacing = wordSpacingOverwrite)
398
399 val paragraph = simpleParagraph(
400 text = text,
401 textStyles = listOf(
402 ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length),
403 ParagraphBuilder.TextStyleIndex(textStyleOverwrite, 0, "abc".length)
404 )
405 )
406 // Notice that the width doesn't matter for this test.
407 paragraph.layout(100.0f)
408
409 assertThat(paragraph.underlyingText.toString(), equalTo(text))
410 assertThat(
411 paragraph.underlyingText,
412 hasSpan(WordSpacingSpan::class, 0, text.length) { span ->
413 span.wordSpacing == wordSpacing.toFloat()
414 }
415 )
416 assertThat(
417 paragraph.underlyingText,
418 hasSpan(WordSpacingSpan::class, 0, "abc".length) { span ->
419 span.wordSpacing == wordSpacingOverwrite.toFloat()
420 }
421 )
422 assertThat(
423 paragraph.underlyingText,
424 hasSpanOnTop(WordSpacingSpan::class, 0, "abc".length) { span ->
425 span.wordSpacing == wordSpacingOverwrite.toFloat()
426 }
427 )
428 }
429
430 @Test
Haoyu Zhang0c4fd022019-01-02 13:53:10 -0800431 fun textStyle_setBackgroundOnWholeText() {
432 val text = "abcde"
433 val color = Color(0xFF0000FF.toInt())
Haoyu Zhang437d9452019-01-04 17:34:04 -0800434 val textStyle = TextStyle(background = color)
Haoyu Zhang0c4fd022019-01-02 13:53:10 -0800435
436 val paragraph = simpleParagraph(
437 text = text,
438 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
439 )
George Mountb1cdd862018-12-21 10:54:42 -0800440 paragraph.layout(100.0f)
Haoyu Zhang0c4fd022019-01-02 13:53:10 -0800441
442 assertThat(paragraph.underlyingText.toString(), equalTo(text))
443 assertThat(paragraph.underlyingText,
444 hasSpan(BackgroundColorSpan::class, 0, text.length) { span ->
445 span.backgroundColor == color.value
446 }
447 )
448 }
449
450 @Test
451 fun textStyle_setBackgroundOnPartText() {
452 val text = "abcde"
453 val color = Color(0xFF0000FF.toInt())
Haoyu Zhang437d9452019-01-04 17:34:04 -0800454 val textStyle = TextStyle(background = color)
Haoyu Zhang0c4fd022019-01-02 13:53:10 -0800455
456 val paragraph = simpleParagraph(
457 text = text,
458 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length))
459 )
George Mountb1cdd862018-12-21 10:54:42 -0800460 paragraph.layout(100.0f)
Haoyu Zhang0c4fd022019-01-02 13:53:10 -0800461
462 assertThat(paragraph.underlyingText.toString(), equalTo(text))
463 assertThat(paragraph.underlyingText,
464 hasSpan(BackgroundColorSpan::class, 0, "abc".length) { span ->
465 span.backgroundColor == color.value
466 }
467 )
468 }
469
470 @Test
471 fun textStyle_setBackgroundTwice_lastOneOverwrite() {
472 val text = "abcde"
473 val color = Color(0xFF0000FF.toInt())
Haoyu Zhang437d9452019-01-04 17:34:04 -0800474 val textStyle = TextStyle(background = color)
Haoyu Zhang0c4fd022019-01-02 13:53:10 -0800475 val colorOverwrite = Color(0xFF00FF00.toInt())
Haoyu Zhang437d9452019-01-04 17:34:04 -0800476 val textStyleOverwrite = TextStyle(background = colorOverwrite)
Haoyu Zhang0c4fd022019-01-02 13:53:10 -0800477
478 val paragraph = simpleParagraph(
479 text = text,
480 textStyles = listOf(
481 ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length),
482 ParagraphBuilder.TextStyleIndex(textStyleOverwrite, 0, "abc".length)
483 )
484 )
George Mountb1cdd862018-12-21 10:54:42 -0800485 paragraph.layout(100.0f)
Haoyu Zhang0c4fd022019-01-02 13:53:10 -0800486
487 assertThat(paragraph.underlyingText.toString(), equalTo(text))
488 assertThat(paragraph.underlyingText,
489 hasSpan(BackgroundColorSpan::class, 0, text.length) { span ->
490 span.backgroundColor == color.value
491 }
492 )
493 assertThat(paragraph.underlyingText,
494 hasSpan(BackgroundColorSpan::class, 0, "abc".length) { span ->
495 span.backgroundColor == colorOverwrite.value
496 }
497 )
498 assertThat(
499 paragraph.underlyingText,
500 hasSpanOnTop(BackgroundColorSpan::class, 0, "abc".length) { span ->
501 span.backgroundColor == colorOverwrite.value
502 }
503 )
504 }
505
506 @Test
Haoyu Zhang4b201d22018-12-18 14:27:38 -0800507 fun textStyle_setLocaleOnWholeText() {
508 val text = "abcde"
509 val locale = Locale("en", "US")
510 val textStyle = TextStyle(locale = locale)
511
512 val paragraph = simpleParagraph(
513 text = text,
514 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
515 )
George Mountb1cdd862018-12-21 10:54:42 -0800516 paragraph.layout(100.0f)
Haoyu Zhang4b201d22018-12-18 14:27:38 -0800517
518 assertThat(paragraph.underlyingText, hasSpan(LocaleSpan::class, 0, text.length))
519 }
520
521 @Test
522 fun textStyle_setLocaleOnPartText() {
523 val text = "abcde"
524 val locale = Locale("en", "US")
525 val textStyle = TextStyle(locale = locale)
526
527 val paragraph = simpleParagraph(
528 text = text,
529 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length))
530 )
George Mountb1cdd862018-12-21 10:54:42 -0800531 paragraph.layout(100.0f)
Haoyu Zhang4b201d22018-12-18 14:27:38 -0800532
533 assertThat(paragraph.underlyingText, hasSpan(LocaleSpan::class, 0, "abc".length))
534 }
535
536 @Test
537 fun textStyle_setLocaleTwice_lastOneOverwrite() {
538 val text = "abcde"
539 val textStyle = TextStyle(locale = Locale("en", "US"))
540 val textStyleOverwrite = TextStyle(locale = Locale("ja", "JP"))
541
542 val paragraph = simpleParagraph(
543 text = text,
544 textStyles = listOf(
545 ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length),
546 ParagraphBuilder.TextStyleIndex(textStyleOverwrite, 0, "abc".length)
547 )
548 )
George Mountb1cdd862018-12-21 10:54:42 -0800549 paragraph.layout(100.0f)
Haoyu Zhang4b201d22018-12-18 14:27:38 -0800550
551 assertThat(paragraph.underlyingText, hasSpan(LocaleSpan::class, 0, text.length))
552 assertThat(paragraph.underlyingText, hasSpan(LocaleSpan::class, 0, "abc".length))
553 assertThat(
554 paragraph.underlyingText,
555 hasSpanOnTop(LocaleSpan::class, 0, "abc".length)
556 )
557 }
558
559 @Test
Haoyu Zhang40ce6bf2019-01-28 15:58:20 -0800560 fun textStyle_setBaselineShiftOnWholeText() {
561 val text = "abcde"
562 val textStyle = TextStyle(baselineShift = BaselineShift.SUBSCRIPT)
563
564 val paragraph = simpleParagraph(
565 text = text,
566 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
567 )
568 // width is not important
569 paragraph.layout(100.0f)
570
571 assertThat(paragraph.underlyingText, hasSpan(BaselineShiftSpan::class, 0, text.length))
572 }
573
574 @Test
575 fun textStyle_setBaselineShiftOnPartText() {
576 val text = "abcde"
577 val textStyle = TextStyle(baselineShift = BaselineShift.SUPERSCRIPT)
578
579 val paragraph = simpleParagraph(
580 text = text,
581 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length))
582 )
583 // width is not important
584 paragraph.layout(100.0f)
585
586 assertThat(paragraph.underlyingText, hasSpan(BaselineShiftSpan::class, 0, "abc".length))
587 }
588
589 @Test
590 fun textStyle_setBaselineShiftTwice_LastOneOnTop() {
591 val text = "abcde"
592 val textStyle = TextStyle(baselineShift = BaselineShift.SUBSCRIPT)
593 val textStyleOverwrite = TextStyle(baselineShift = BaselineShift.SUPERSCRIPT)
594
595 val paragraph = simpleParagraph(
596 text = text,
597 textStyles = listOf(
598 ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length),
599 ParagraphBuilder.TextStyleIndex(textStyleOverwrite, 0, "abc".length)
600 )
601 )
602 // width is not important
603 paragraph.layout(100.0f)
604
605 assertThat(paragraph.underlyingText, hasSpan(BaselineShiftSpan::class, 0, text.length))
606 assertThat(paragraph.underlyingText, hasSpan(BaselineShiftSpan::class, 0, "abc".length))
607 assertThat(
608 paragraph.underlyingText,
609 hasSpanOnTop(BaselineShiftSpan::class, 0, "abc".length)
610 )
611 }
612
613 @Test
Haoyu Zhang15750922019-02-19 13:44:17 -0800614 fun textStyle_setTextGeometricTransformWithNull_noSpanSet() {
615 val text = "abcde"
616 val textStyle = TextStyle(textGeometricTransform = TextGeometricTransform(null, null))
617
618 val paragraph = simpleParagraph(
619 text = text,
620 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
621 )
622 // width is not important
623 paragraph.layout(100.0f)
624
625 assertThat(paragraph.underlyingText, not(hasSpan(ScaleXSpan::class, 0, text.length)))
626 assertThat(paragraph.underlyingText, not(hasSpan(SkewXSpan::class, 0, text.length)))
627 }
628
629 @Test
630 fun textStyle_setTextGeometricTransformWithScaleX() {
631 val text = "abcde"
632 val scaleX = 0.5f
633 val textStyle = TextStyle(textGeometricTransform = TextGeometricTransform(scaleX, null))
634
635 val paragraph = simpleParagraph(
636 text = text,
637 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
638 )
639 // width is not important
640 paragraph.layout(100.0f)
641
642 assertThat(
643 paragraph.underlyingText,
644 hasSpan(ScaleXSpan::class, 0, text.length) { it.scaleX == scaleX }
645 )
646 assertThat(paragraph.underlyingText, not(hasSpan(SkewXSpan::class, 0, text.length)))
647 }
648
649 @Test
650 fun textStyle_setTextGeometricTransformWithSkewX() {
651 val text = "aa"
652 val skewX = 1f
653 val textStyle = TextStyle(textGeometricTransform = TextGeometricTransform(null, skewX))
654
655 val paragraph = simpleParagraph(
656 text = text,
657 textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
658 )
659 // width is not important
660 paragraph.layout(100.0f)
661
662 assertThat(
663 paragraph.underlyingText,
664 hasSpan(SkewXSpan::class, 0, text.length) { it.skewX == skewX }
665 )
666 assertThat(paragraph.underlyingText, not(hasSpan(ScaleXSpan::class, 0, text.length)))
667 }
668
669 @Test
Siyamed Sinir70f336a2018-12-27 10:37:09 -0800670 fun textStyle_fontFamily_addsTypefaceSpanWithCorrectTypeface() {
671 val text = "abcde"
672 val textStyle = TextStyle(
673 fontFamily = fontFamily,
674 fontStyle = FontStyle.italic,
675 fontWeight = FontWeight.bold
676 )
677 val expectedTypeface = TypefaceAdapter().create(
678 fontFamily = fontFamily,
679 fontStyle = FontStyle.italic,
680 fontWeight = FontWeight.bold
681 )
Siyamed Sinir37298412018-12-27 19:47:15 -0800682 val expectedStart = 0
683 val expectedEnd = "abc".length
Siyamed Sinir70f336a2018-12-27 10:37:09 -0800684
685 val paragraph = simpleParagraph(
686 text = text,
Siyamed Sinir37298412018-12-27 19:47:15 -0800687 textStyles = listOf(
688 ParagraphBuilder.TextStyleIndex(
689 textStyle,
690 expectedStart,
691 expectedEnd
692 )
693 )
Siyamed Sinir70f336a2018-12-27 10:37:09 -0800694 )
George Mountb1cdd862018-12-21 10:54:42 -0800695 paragraph.layout(100.0f)
Siyamed Sinir70f336a2018-12-27 10:37:09 -0800696
697 assertThat(paragraph.underlyingText.toString(), equalTo(text))
698 assertThat(
699 paragraph.underlyingText,
Siyamed Sinir37298412018-12-27 19:47:15 -0800700 hasSpan(TypefaceSpan::class, expectedStart, expectedEnd) { span ->
Siyamed Sinir70f336a2018-12-27 10:37:09 -0800701 span.typeface == expectedTypeface
Siyamed Sinir37298412018-12-27 19:47:15 -0800702 })
703 }
704
705 @Test
706 fun textStyle_fontFamily_whenFontSynthesizeTurnedOff() {
707 val text = "abcde"
708 val textStyle = TextStyle(
709 fontFamily = fontFamily,
710 fontStyle = FontStyle.italic,
711 fontWeight = FontWeight.bold,
712 fontSynthesis = FontSynthesis.none
Siyamed Sinir70f336a2018-12-27 10:37:09 -0800713 )
Siyamed Sinir37298412018-12-27 19:47:15 -0800714 val expectedTypeface = TypefaceAdapter().create(
715 fontFamily = fontFamily,
716 fontStyle = FontStyle.italic,
717 fontWeight = FontWeight.bold,
718 fontSynthesis = FontSynthesis.none
719 )
720 val expectedStart = 0
721 val expectedEnd = "abc".length
722
723 val paragraph = simpleParagraph(
724 text = text,
725 textStyles = listOf(
726 ParagraphBuilder.TextStyleIndex(
727 textStyle,
728 expectedStart,
729 expectedEnd
730 )
731 )
732 )
George Mountb1cdd862018-12-21 10:54:42 -0800733 paragraph.layout(100.0f)
Siyamed Sinir37298412018-12-27 19:47:15 -0800734
735 assertThat(paragraph.underlyingText.toString(), equalTo(text))
736 assertThat(
737 paragraph.underlyingText,
738 hasSpan(TypefaceSpan::class, expectedStart, expectedEnd) { span ->
739 span.typeface == expectedTypeface
740 })
Siyamed Sinir70f336a2018-12-27 10:37:09 -0800741 }
742
743 @Test
Siyamed Sinirf18dcca2018-12-20 18:25:10 -0800744 fun testEmptyFontFamily() {
745 val typefaceAdapter = mock<TypefaceAdapter>()
746 val paragraph = simpleParagraph(
747 text = "abc",
748 typefaceAdapter = typefaceAdapter
749 )
George Mountb1cdd862018-12-21 10:54:42 -0800750 paragraph.layout(Float.MAX_VALUE)
Siyamed Sinirf18dcca2018-12-20 18:25:10 -0800751
752 verify(typefaceAdapter, never()).create(
753 fontFamily = any(),
754 fontWeight = any(),
Siyamed Sinir21796012018-12-26 13:51:17 -0800755 fontStyle = any(),
756 fontSynthesis = any()
Siyamed Sinirf18dcca2018-12-20 18:25:10 -0800757 )
758 assertThat(paragraph.textPaint.typeface, nullValue())
759 }
760
761 @Test
762 fun testEmptyFontFamily_withBoldFontWeightSelection() {
763 val typefaceAdapter = spy(TypefaceAdapter())
764
765 val paragraph = simpleParagraph(
766 text = "abc",
767 fontFamily = null,
768 fontWeight = FontWeight.bold,
769 typefaceAdapter = typefaceAdapter
770 )
George Mountb1cdd862018-12-21 10:54:42 -0800771 paragraph.layout(Float.MAX_VALUE)
Siyamed Sinirf18dcca2018-12-20 18:25:10 -0800772
773 verify(typefaceAdapter, times(1)).create(
774 fontFamily = eq(null),
775 fontWeight = eq(FontWeight.bold),
Siyamed Sinir21796012018-12-26 13:51:17 -0800776 fontStyle = eq(FontStyle.normal),
777 fontSynthesis = eq(FontSynthesis.all)
Siyamed Sinirf18dcca2018-12-20 18:25:10 -0800778 )
779
780 val typeface = paragraph.textPaint.typeface
781 assertThat(typeface, not(nullValue()))
782 assertThat(typeface.isBold, equalTo(true))
783 assertThat(typeface.isItalic, equalTo(false))
784 }
785
786 @Test
787 fun testEmptyFontFamily_withFontStyleSelection() {
788 val typefaceAdapter = spy(TypefaceAdapter())
789 val paragraph = simpleParagraph(
790 text = "abc",
791 fontFamily = null,
792 fontStyle = FontStyle.italic,
793 typefaceAdapter = typefaceAdapter
794 )
George Mountb1cdd862018-12-21 10:54:42 -0800795 paragraph.layout(Float.MAX_VALUE)
Siyamed Sinirf18dcca2018-12-20 18:25:10 -0800796
797 verify(typefaceAdapter, times(1)).create(
798 fontFamily = eq(null),
799 fontWeight = eq(FontWeight.normal),
Siyamed Sinir21796012018-12-26 13:51:17 -0800800 fontStyle = eq(FontStyle.italic),
801 fontSynthesis = eq(FontSynthesis.all)
Siyamed Sinirf18dcca2018-12-20 18:25:10 -0800802 )
803
804 val typeface = paragraph.textPaint.typeface
805 assertThat(typeface, not(nullValue()))
806 assertThat(typeface.isBold, equalTo(false))
807 assertThat(typeface.isItalic, equalTo(true))
808 }
809
810 @Test
811 fun testFontFamily_withGenericFamilyName() {
812 val typefaceAdapter = spy(TypefaceAdapter())
813 val fontFamily = FontFamily("sans-serif")
814
815 val paragraph = simpleParagraph(
816 text = "abc",
817 fontFamily = fontFamily,
818 typefaceAdapter = typefaceAdapter
819 )
George Mountb1cdd862018-12-21 10:54:42 -0800820 paragraph.layout(Float.MAX_VALUE)
Siyamed Sinirf18dcca2018-12-20 18:25:10 -0800821
822 verify(typefaceAdapter, times(1)).create(
823 fontFamily = eq(fontFamily),
824 fontWeight = eq(FontWeight.normal),
Siyamed Sinir21796012018-12-26 13:51:17 -0800825 fontStyle = eq(FontStyle.normal),
826 fontSynthesis = eq(FontSynthesis.all)
Siyamed Sinirf18dcca2018-12-20 18:25:10 -0800827 )
828
829 val typeface = paragraph.textPaint.typeface
830 assertThat(typeface, not(nullValue()))
831 assertThat(typeface.isBold, equalTo(false))
832 assertThat(typeface.isItalic, equalTo(false))
833 }
834
835 @Test
836 fun testFontFamily_withCustomFont() {
837 val typefaceAdapter = spy(TypefaceAdapter())
838 val paragraph = simpleParagraph(
839 text = "abc",
840 fontFamily = fontFamily,
841 typefaceAdapter = typefaceAdapter
842 )
George Mountb1cdd862018-12-21 10:54:42 -0800843 paragraph.layout(Float.MAX_VALUE)
Siyamed Sinirf18dcca2018-12-20 18:25:10 -0800844
845 verify(typefaceAdapter, times(1)).create(
846 fontFamily = eq(fontFamily),
847 fontWeight = eq(FontWeight.normal),
Siyamed Sinir21796012018-12-26 13:51:17 -0800848 fontStyle = eq(FontStyle.normal),
849 fontSynthesis = eq(FontSynthesis.all)
Siyamed Sinirf18dcca2018-12-20 18:25:10 -0800850 )
851 val typeface = paragraph.textPaint.typeface
852 assertThat(typeface.isBold, equalTo(false))
853 assertThat(typeface.isItalic, equalTo(false))
854 }
855
Haoyu Zhangc11ef5a2019-01-14 16:35:05 -0800856 @Test
857 fun testEllipsis_withMaxLineEqualsNull_doesNotEllipsis() {
858 val text = "abc"
859 val fontSize = 20f
860 val paragraphWidth = (text.length - 1) * fontSize
861 val paragraph = simpleParagraph(
862 text = text,
863 fontFamily = fontFamily,
864 fontSize = fontSize,
865 ellipsis = true
866 )
867 paragraph.layout(paragraphWidth)
868 for (i in 0 until paragraph.lineCount) {
869 assertFalse(paragraph.isEllipsisApplied(i))
870 }
871 }
872
873 @Test
874 fun testEllipsis_withMaxLinesLessThanTextLines_doesEllipsis() {
875 val text = "abcde"
876 val fontSize = 100f
877 // Note that on API 21, if the next line only contains 1 character, ellipsis won't work
878 val paragraphWidth = (text.length - 1.5f) * fontSize
879 val paragraph = simpleParagraph(
880 text = text,
881 fontFamily = fontFamily,
882 fontSize = fontSize,
883 ellipsis = true,
884 maxLines = 1
885 )
886 paragraph.layout(paragraphWidth)
887
888 assertTrue(paragraph.isEllipsisApplied(0))
889 }
890
891 @Test
892 fun testEllipsis_withMaxLinesMoreThanTextLines_doesNotEllipsis() {
893 val text = "abc"
894 val fontSize = 100f
895 val paragraphWidth = (text.length - 1) * fontSize
896 val maxLines = ceil(text.length * fontSize / paragraphWidth).toInt()
897 val paragraph = simpleParagraph(
898 text = text,
899 fontFamily = fontFamily,
900 fontSize = fontSize,
901 ellipsis = true,
902 maxLines = maxLines
903 )
904 paragraph.layout(paragraphWidth)
905
906 for (i in 0 until paragraph.lineCount) {
907 assertFalse(paragraph.isEllipsisApplied(i))
908 }
909 }
910
Haoyu Zhang10fc6502018-11-09 15:50:11 -0800911 private fun simpleParagraph(
912 text: CharSequence = "",
Haoyu Zhang47f57a22018-12-12 13:16:40 -0800913 textStyles: List<ParagraphBuilder.TextStyleIndex> = listOf(),
Haoyu Zhang10fc6502018-11-09 15:50:11 -0800914 textAlign: TextAlign? = null,
George Mountb1cdd862018-12-21 10:54:42 -0800915 fontSize: Float? = null,
Haoyu Zhangc11ef5a2019-01-14 16:35:05 -0800916 ellipsis: Boolean? = null,
Siyamed Sinirf18dcca2018-12-20 18:25:10 -0800917 maxLines: Int? = null,
918 fontFamily: FontFamily? = null,
919 fontWeight: FontWeight? = null,
920 fontStyle: FontStyle? = null,
921 typefaceAdapter: TypefaceAdapter = TypefaceAdapter()
Haoyu Zhang10fc6502018-11-09 15:50:11 -0800922 ): ParagraphAndroid {
923 return ParagraphAndroid(
924 text = StringBuilder(text),
Haoyu Zhang47f57a22018-12-12 13:16:40 -0800925 textStyles = textStyles,
Siyamed Sinirf18dcca2018-12-20 18:25:10 -0800926 typefaceAdapter = typefaceAdapter,
Haoyu Zhang10fc6502018-11-09 15:50:11 -0800927 paragraphStyle = ParagraphStyle(
928 textAlign = textAlign,
Haoyu Zhangc11ef5a2019-01-14 16:35:05 -0800929 ellipsis = ellipsis,
Haoyu Zhang10fc6502018-11-09 15:50:11 -0800930 maxLines = maxLines,
Siyamed Sinirf18dcca2018-12-20 18:25:10 -0800931 fontFamily = fontFamily,
932 fontSize = fontSize,
933 fontWeight = fontWeight,
934 fontStyle = fontStyle
Haoyu Zhang10fc6502018-11-09 15:50:11 -0800935 )
936 )
937 }
George Mountb1cdd862018-12-21 10:54:42 -0800938}