Move text classes from port to text
Does not make any changes to the code. With this change ui-port depends
on ui-text and only the lower layer is migrated. There are other classes
Such as TextPainter, TextSelection, RenderParagraph which are closer to
upper layer. Those classes will be ported in a separate CL.
Test: gradlew ui-port:test
Test: gradlew ui-port:connectedAndroidTest
Test: gradlew ui-text:test
Test: gradlew ui-text:connectedAndroidTest
Bug: 123712369
Change-Id: I60c3423606caff49e4a25da49a6c2a803da6e5fe
diff --git a/ui/text/src/androidTest/java/androidx/ui/engine/text/platform/ParagraphAndroidTest.kt b/ui/text/src/androidTest/java/androidx/ui/engine/text/platform/ParagraphAndroidTest.kt
new file mode 100644
index 0000000..cdf9e77c
--- /dev/null
+++ b/ui/text/src/androidTest/java/androidx/ui/engine/text/platform/ParagraphAndroidTest.kt
@@ -0,0 +1,879 @@
+package androidx.ui.port.engine.text.platform
+
+import android.graphics.Paint
+import android.text.TextPaint
+import android.text.style.AbsoluteSizeSpan
+import android.text.style.BackgroundColorSpan
+import android.text.style.ForegroundColorSpan
+import android.text.style.LocaleSpan
+import android.text.style.StrikethroughSpan
+import android.text.style.UnderlineSpan
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.text.StaticLayoutCompat
+import androidx.text.style.BaselineShiftSpan
+import androidx.text.style.LetterSpacingSpan
+import androidx.text.style.TypefaceSpan
+import androidx.text.style.WordSpacingSpan
+import androidx.ui.engine.text.BaselineShift
+import androidx.ui.engine.text.FontStyle
+import androidx.ui.engine.text.FontSynthesis
+import androidx.ui.engine.text.FontWeight
+import androidx.ui.engine.text.ParagraphBuilder
+import androidx.ui.engine.text.ParagraphStyle
+import androidx.ui.engine.text.TextAlign
+import androidx.ui.engine.text.TextDecoration
+import androidx.ui.engine.text.TextStyle
+import androidx.ui.engine.text.font.FontFamily
+import androidx.ui.engine.text.font.asFontFamily
+import androidx.ui.engine.text.platform.ParagraphAndroid
+import androidx.ui.engine.text.platform.TypefaceAdapter
+import androidx.ui.engine.window.Locale
+import androidx.ui.painting.Color
+import androidx.ui.engine.text.FontTestData.Companion.BASIC_MEASURE_FONT
+import androidx.ui.matchers.equalToBitmap
+import androidx.ui.matchers.hasSpan
+import androidx.ui.matchers.hasSpanOnTop
+import com.nhaarman.mockitokotlin2.any
+import com.nhaarman.mockitokotlin2.eq
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.never
+import com.nhaarman.mockitokotlin2.spy
+import com.nhaarman.mockitokotlin2.times
+import com.nhaarman.mockitokotlin2.verify
+import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.not
+import org.hamcrest.Matchers.nullValue
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertThat
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import kotlin.math.ceil
+
+@RunWith(JUnit4::class)
+@SmallTest
+class ParagraphAndroidTest {
+ private lateinit var fontFamily: FontFamily
+
+ @Before
+ fun setup() {
+ // This sample font provides the following features:
+ // 1. The width of most of visible characters equals to font size.
+ // 2. The LTR/RTL characters are rendered as ▶/◀.
+ // 3. The fontMetrics passed to TextPaint has descend - ascend equal to 1.2 * fontSize.
+ fontFamily = BASIC_MEASURE_FONT.asFontFamily()
+ fontFamily.context = InstrumentationRegistry.getInstrumentation().context
+ }
+
+ @Test
+ fun draw_with_newline_and_line_break_default_values() {
+ val fontSize = 50.0f
+ for (text in arrayOf("abc\ndef", "\u05D0\u05D1\u05D2\n\u05D3\u05D4\u05D5")) {
+ val paragraphAndroid = simpleParagraph(
+ text = StringBuilder(text),
+ fontSize = fontSize,
+ fontFamily = fontFamily
+ )
+
+ // 2 chars width
+ paragraphAndroid.layout(width = 2 * fontSize)
+
+ val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
+ textPaint.textSize = fontSize
+ textPaint.typeface = TypefaceAdapter().create(fontFamily)
+
+ val staticLayout = StaticLayoutCompat.Builder(
+ text,
+ textPaint,
+ ceil(paragraphAndroid.width).toInt()
+ )
+ .setEllipsizedWidth(ceil(paragraphAndroid.width).toInt())
+ .build()
+ assertThat(paragraphAndroid.bitmap(), equalToBitmap(staticLayout.bitmap()))
+ }
+ }
+
+ @Test
+ fun textStyle_setColorOnWholeText() {
+ val text = "abcde"
+ val textStyle = TextStyle(color = Color(0xFF0000FF.toInt()))
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
+ )
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText, hasSpan(ForegroundColorSpan::class, 0, text.length))
+ }
+
+ @Test
+ fun textStyle_setColorOnPartOfText() {
+ val text = "abcde"
+ val textStyle = TextStyle(color = Color(0xFF0000FF.toInt()))
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length))
+ )
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText, hasSpan(ForegroundColorSpan::class, 0, "abc".length))
+ }
+
+ @Test
+ fun textStyle_setColorTwice_lastOneOverwrite() {
+ val text = "abcde"
+ val textStyle = TextStyle(color = Color(0xFF0000FF.toInt()))
+ val textStyleOverwrite = TextStyle(color = Color(0xFF00FF00.toInt()))
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(
+ ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length),
+ ParagraphBuilder.TextStyleIndex(textStyleOverwrite, 0, "abc".length)
+ )
+ )
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText, hasSpan(ForegroundColorSpan::class, 0, text.length))
+ assertThat(paragraph.underlyingText, hasSpan(ForegroundColorSpan::class, 0, "abc".length))
+ assertThat(
+ paragraph.underlyingText,
+ hasSpanOnTop(ForegroundColorSpan::class, 0, "abc".length)
+ )
+ }
+
+ @Test
+ fun testStyle_setTextDecorationOnWholeText_withLineThrough() {
+ val text = "abcde"
+ val textStyle = TextStyle(decoration = TextDecoration.lineThrough)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
+ )
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText.toString(), equalTo(text))
+ assertThat(paragraph.underlyingText, hasSpan(StrikethroughSpan::class, 0, text.length))
+ }
+
+ @Test
+ fun testStyle_setTextDecorationOnWholeText_withUnderline() {
+ val text = "abcde"
+ val textStyle = TextStyle(decoration = TextDecoration.underline)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
+ )
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText.toString(), equalTo(text))
+ assertThat(paragraph.underlyingText, hasSpan(UnderlineSpan::class, 0, text.length))
+ }
+
+ @Test
+ fun testStyle_setTextDecorationOnPartText_withLineThrough() {
+ val text = "abcde"
+ val textStyle = TextStyle(decoration = TextDecoration.lineThrough)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length))
+ )
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText.toString(), equalTo(text))
+ assertThat(paragraph.underlyingText, hasSpan(StrikethroughSpan::class, 0, "abc".length))
+ }
+
+ @Test
+ fun testStyle_setTextDecorationOnPartText_withUnderline() {
+ val text = "abcde"
+ val textStyle = TextStyle(decoration = TextDecoration.underline)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length))
+ )
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText.toString(), equalTo(text))
+ assertThat(paragraph.underlyingText, hasSpan(UnderlineSpan::class, 0, "abc".length))
+ }
+
+ @Test
+ fun testStyle_setTextDecoration_withLineThroughAndUnderline() {
+ val text = "abcde"
+ val textStyle = TextStyle(
+ decoration = TextDecoration.combine(
+ listOf(TextDecoration.lineThrough, TextDecoration.underline)
+ )
+ )
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length))
+ )
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText.toString(), equalTo(text))
+ assertThat(paragraph.underlyingText, hasSpan(UnderlineSpan::class, 0, "abc".length))
+ assertThat(paragraph.underlyingText, hasSpan(StrikethroughSpan::class, 0, "abc".length))
+ }
+
+ @Test
+ fun textStyle_setFontSizeOnWholeText() {
+ val text = "abcde"
+ val fontSize = 20.0f
+ val paragraphWidth = text.length * fontSize
+ val textStyle = TextStyle(fontSize = fontSize)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
+ )
+ paragraph.layout(paragraphWidth)
+
+ assertThat(paragraph.underlyingText, hasSpan(AbsoluteSizeSpan::class, 0, text.length))
+ }
+
+ @Test
+ fun textStyle_setFontSizeOnPartText() {
+ val text = "abcde"
+ val fontSize = 20.0f
+ val paragraphWidth = text.length * fontSize
+ val textStyle = TextStyle(fontSize = fontSize)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length))
+ )
+ paragraph.layout(paragraphWidth)
+
+ assertThat(paragraph.underlyingText, hasSpan(AbsoluteSizeSpan::class, 0, "abc".length))
+ }
+
+ @Test
+ fun textStyle_setFontSizeTwice_lastOneOverwrite() {
+ val text = "abcde"
+ val fontSize = 20.0f
+ val fontSizeOverwrite = 30.0f
+ val paragraphWidth = text.length * fontSizeOverwrite
+ val textStyle = TextStyle(fontSize = fontSize)
+ val textStyleOverwrite = TextStyle(fontSize = fontSizeOverwrite)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(
+ ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length),
+ ParagraphBuilder.TextStyleIndex(textStyleOverwrite, 0, "abc".length)
+ )
+ )
+ paragraph.layout(paragraphWidth)
+
+ assertThat(paragraph.underlyingText, hasSpan(AbsoluteSizeSpan::class, 0, text.length))
+ assertThat(paragraph.underlyingText, hasSpan(AbsoluteSizeSpan::class, 0, "abc".length))
+ assertThat(
+ paragraph.underlyingText,
+ hasSpanOnTop(AbsoluteSizeSpan::class, 0, "abc".length)
+ )
+ }
+
+ @Test
+ fun textStyle_setLetterSpacingOnWholeText() {
+ val text = "abcde"
+ val letterSpacing = 2.0f
+ val textStyle = TextStyle(letterSpacing = letterSpacing)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
+ )
+ paragraph.layout(100.0f)
+ assertThat(paragraph.underlyingText.toString(), equalTo(text))
+ assertThat(paragraph.underlyingText, hasSpan(LetterSpacingSpan::class, 0, text.length))
+ }
+
+ @Test
+ fun textStyle_setLetterSpacingOnPartText() {
+ val text = "abcde"
+ val textStyle = TextStyle(letterSpacing = 2.0f)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length))
+ )
+ paragraph.layout(100.0f)
+ assertThat(paragraph.underlyingText.toString(), equalTo(text))
+ assertThat(paragraph.underlyingText, hasSpan(LetterSpacingSpan::class, 0, "abc".length))
+ }
+
+ @Test
+ fun textStyle_setLetterSpacingTwice_lastOneOverwrite() {
+ val text = "abcde"
+ val textStyle = TextStyle(letterSpacing = 2.0f)
+ val textStyleOverwrite = TextStyle(letterSpacing = 3.0f)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(
+ ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length),
+ ParagraphBuilder.TextStyleIndex(textStyleOverwrite, 0, "abc".length)
+ )
+ )
+ paragraph.layout(100.0f)
+ assertThat(paragraph.underlyingText.toString(), equalTo(text))
+ assertThat(paragraph.underlyingText, hasSpan(LetterSpacingSpan::class, 0, text.length))
+ assertThat(paragraph.underlyingText, hasSpan(LetterSpacingSpan::class, 0, "abc".length))
+ assertThat(
+ paragraph.underlyingText,
+ hasSpanOnTop(LetterSpacingSpan::class, 0, "abc".length)
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ fun textStyle_setWordSpacingOnWholeText() {
+ val text = "ab cd"
+ val wordSpacing = 2.0f
+ val textStyle = TextStyle(wordSpacing = wordSpacing)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
+ )
+ // Notice that the width doesn't matter for this test.
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText.toString(), equalTo(text))
+ assertThat(
+ paragraph.underlyingText,
+ hasSpan(WordSpacingSpan::class, 0, text.length) { span ->
+ span.wordSpacing == wordSpacing.toFloat()
+ }
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ fun textStyle_setWordSpacingOnPartText() {
+ val text = "abc d"
+ val wordSpacing = 2.0f
+ val textStyle = TextStyle(wordSpacing = wordSpacing)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length))
+ )
+ // Notice that the width doesn't matter for this test.
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText.toString(), equalTo(text))
+ assertThat(
+ paragraph.underlyingText,
+ hasSpan(WordSpacingSpan::class, 0, "abc".length) { span ->
+ span.wordSpacing == wordSpacing.toFloat()
+ }
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ fun textStyle_setWordSpacingTwice_lastOneOverwrite() {
+ val text = "abc d"
+ val wordSpacing = 2.0f
+ val textStyle = TextStyle(wordSpacing = wordSpacing)
+ val wordSpacingOverwrite = 3.0f
+ val textStyleOverwrite = TextStyle(wordSpacing = wordSpacingOverwrite)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(
+ ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length),
+ ParagraphBuilder.TextStyleIndex(textStyleOverwrite, 0, "abc".length)
+ )
+ )
+ // Notice that the width doesn't matter for this test.
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText.toString(), equalTo(text))
+ assertThat(
+ paragraph.underlyingText,
+ hasSpan(WordSpacingSpan::class, 0, text.length) { span ->
+ span.wordSpacing == wordSpacing.toFloat()
+ }
+ )
+ assertThat(
+ paragraph.underlyingText,
+ hasSpan(WordSpacingSpan::class, 0, "abc".length) { span ->
+ span.wordSpacing == wordSpacingOverwrite.toFloat()
+ }
+ )
+ assertThat(
+ paragraph.underlyingText,
+ hasSpanOnTop(WordSpacingSpan::class, 0, "abc".length) { span ->
+ span.wordSpacing == wordSpacingOverwrite.toFloat()
+ }
+ )
+ }
+
+ @Test
+ fun textStyle_setBackgroundOnWholeText() {
+ val text = "abcde"
+ val color = Color(0xFF0000FF.toInt())
+ val textStyle = TextStyle(background = color)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
+ )
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText.toString(), equalTo(text))
+ assertThat(paragraph.underlyingText,
+ hasSpan(BackgroundColorSpan::class, 0, text.length) { span ->
+ span.backgroundColor == color.value
+ }
+ )
+ }
+
+ @Test
+ fun textStyle_setBackgroundOnPartText() {
+ val text = "abcde"
+ val color = Color(0xFF0000FF.toInt())
+ val textStyle = TextStyle(background = color)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length))
+ )
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText.toString(), equalTo(text))
+ assertThat(paragraph.underlyingText,
+ hasSpan(BackgroundColorSpan::class, 0, "abc".length) { span ->
+ span.backgroundColor == color.value
+ }
+ )
+ }
+
+ @Test
+ fun textStyle_setBackgroundTwice_lastOneOverwrite() {
+ val text = "abcde"
+ val color = Color(0xFF0000FF.toInt())
+ val textStyle = TextStyle(background = color)
+ val colorOverwrite = Color(0xFF00FF00.toInt())
+ val textStyleOverwrite = TextStyle(background = colorOverwrite)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(
+ ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length),
+ ParagraphBuilder.TextStyleIndex(textStyleOverwrite, 0, "abc".length)
+ )
+ )
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText.toString(), equalTo(text))
+ assertThat(paragraph.underlyingText,
+ hasSpan(BackgroundColorSpan::class, 0, text.length) { span ->
+ span.backgroundColor == color.value
+ }
+ )
+ assertThat(paragraph.underlyingText,
+ hasSpan(BackgroundColorSpan::class, 0, "abc".length) { span ->
+ span.backgroundColor == colorOverwrite.value
+ }
+ )
+ assertThat(
+ paragraph.underlyingText,
+ hasSpanOnTop(BackgroundColorSpan::class, 0, "abc".length) { span ->
+ span.backgroundColor == colorOverwrite.value
+ }
+ )
+ }
+
+ @Test
+ fun textStyle_setLocaleOnWholeText() {
+ val text = "abcde"
+ val locale = Locale("en", "US")
+ val textStyle = TextStyle(locale = locale)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
+ )
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText, hasSpan(LocaleSpan::class, 0, text.length))
+ }
+
+ @Test
+ fun textStyle_setLocaleOnPartText() {
+ val text = "abcde"
+ val locale = Locale("en", "US")
+ val textStyle = TextStyle(locale = locale)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length))
+ )
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText, hasSpan(LocaleSpan::class, 0, "abc".length))
+ }
+
+ @Test
+ fun textStyle_setLocaleTwice_lastOneOverwrite() {
+ val text = "abcde"
+ val textStyle = TextStyle(locale = Locale("en", "US"))
+ val textStyleOverwrite = TextStyle(locale = Locale("ja", "JP"))
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(
+ ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length),
+ ParagraphBuilder.TextStyleIndex(textStyleOverwrite, 0, "abc".length)
+ )
+ )
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText, hasSpan(LocaleSpan::class, 0, text.length))
+ assertThat(paragraph.underlyingText, hasSpan(LocaleSpan::class, 0, "abc".length))
+ assertThat(
+ paragraph.underlyingText,
+ hasSpanOnTop(LocaleSpan::class, 0, "abc".length)
+ )
+ }
+
+ @Test
+ fun textStyle_setBaselineShiftOnWholeText() {
+ val text = "abcde"
+ val textStyle = TextStyle(baselineShift = BaselineShift.SUBSCRIPT)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
+ )
+ // width is not important
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText, hasSpan(BaselineShiftSpan::class, 0, text.length))
+ }
+
+ @Test
+ fun textStyle_setBaselineShiftOnPartText() {
+ val text = "abcde"
+ val textStyle = TextStyle(baselineShift = BaselineShift.SUPERSCRIPT)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length))
+ )
+ // width is not important
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText, hasSpan(BaselineShiftSpan::class, 0, "abc".length))
+ }
+
+ @Test
+ fun textStyle_setBaselineShiftTwice_LastOneOnTop() {
+ val text = "abcde"
+ val textStyle = TextStyle(baselineShift = BaselineShift.SUBSCRIPT)
+ val textStyleOverwrite = TextStyle(baselineShift = BaselineShift.SUPERSCRIPT)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(
+ ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length),
+ ParagraphBuilder.TextStyleIndex(textStyleOverwrite, 0, "abc".length)
+ )
+ )
+ // width is not important
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText, hasSpan(BaselineShiftSpan::class, 0, text.length))
+ assertThat(paragraph.underlyingText, hasSpan(BaselineShiftSpan::class, 0, "abc".length))
+ assertThat(
+ paragraph.underlyingText,
+ hasSpanOnTop(BaselineShiftSpan::class, 0, "abc".length)
+ )
+ }
+
+ @Test
+ fun textStyle_fontFamily_addsTypefaceSpanWithCorrectTypeface() {
+ val text = "abcde"
+ val textStyle = TextStyle(
+ fontFamily = fontFamily,
+ fontStyle = FontStyle.italic,
+ fontWeight = FontWeight.bold
+ )
+ val expectedTypeface = TypefaceAdapter().create(
+ fontFamily = fontFamily,
+ fontStyle = FontStyle.italic,
+ fontWeight = FontWeight.bold
+ )
+ val expectedStart = 0
+ val expectedEnd = "abc".length
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(
+ ParagraphBuilder.TextStyleIndex(
+ textStyle,
+ expectedStart,
+ expectedEnd
+ )
+ )
+ )
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText.toString(), equalTo(text))
+ assertThat(
+ paragraph.underlyingText,
+ hasSpan(TypefaceSpan::class, expectedStart, expectedEnd) { span ->
+ span.typeface == expectedTypeface
+ })
+ }
+
+ @Test
+ fun textStyle_fontFamily_whenFontSynthesizeTurnedOff() {
+ val text = "abcde"
+ val textStyle = TextStyle(
+ fontFamily = fontFamily,
+ fontStyle = FontStyle.italic,
+ fontWeight = FontWeight.bold,
+ fontSynthesis = FontSynthesis.none
+ )
+ val expectedTypeface = TypefaceAdapter().create(
+ fontFamily = fontFamily,
+ fontStyle = FontStyle.italic,
+ fontWeight = FontWeight.bold,
+ fontSynthesis = FontSynthesis.none
+ )
+ val expectedStart = 0
+ val expectedEnd = "abc".length
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(
+ ParagraphBuilder.TextStyleIndex(
+ textStyle,
+ expectedStart,
+ expectedEnd
+ )
+ )
+ )
+ paragraph.layout(100.0f)
+
+ assertThat(paragraph.underlyingText.toString(), equalTo(text))
+ assertThat(
+ paragraph.underlyingText,
+ hasSpan(TypefaceSpan::class, expectedStart, expectedEnd) { span ->
+ span.typeface == expectedTypeface
+ })
+ }
+
+ @Test
+ fun testEmptyFontFamily() {
+ val typefaceAdapter = mock<TypefaceAdapter>()
+ val paragraph = simpleParagraph(
+ text = "abc",
+ typefaceAdapter = typefaceAdapter
+ )
+ paragraph.layout(Float.MAX_VALUE)
+
+ verify(typefaceAdapter, never()).create(
+ fontFamily = any(),
+ fontWeight = any(),
+ fontStyle = any(),
+ fontSynthesis = any()
+ )
+ assertThat(paragraph.textPaint.typeface, nullValue())
+ }
+
+ @Test
+ fun testEmptyFontFamily_withBoldFontWeightSelection() {
+ val typefaceAdapter = spy(TypefaceAdapter())
+
+ val paragraph = simpleParagraph(
+ text = "abc",
+ fontFamily = null,
+ fontWeight = FontWeight.bold,
+ typefaceAdapter = typefaceAdapter
+ )
+ paragraph.layout(Float.MAX_VALUE)
+
+ verify(typefaceAdapter, times(1)).create(
+ fontFamily = eq(null),
+ fontWeight = eq(FontWeight.bold),
+ fontStyle = eq(FontStyle.normal),
+ fontSynthesis = eq(FontSynthesis.all)
+ )
+
+ val typeface = paragraph.textPaint.typeface
+ assertThat(typeface, not(nullValue()))
+ assertThat(typeface.isBold, equalTo(true))
+ assertThat(typeface.isItalic, equalTo(false))
+ }
+
+ @Test
+ fun testEmptyFontFamily_withFontStyleSelection() {
+ val typefaceAdapter = spy(TypefaceAdapter())
+ val paragraph = simpleParagraph(
+ text = "abc",
+ fontFamily = null,
+ fontStyle = FontStyle.italic,
+ typefaceAdapter = typefaceAdapter
+ )
+ paragraph.layout(Float.MAX_VALUE)
+
+ verify(typefaceAdapter, times(1)).create(
+ fontFamily = eq(null),
+ fontWeight = eq(FontWeight.normal),
+ fontStyle = eq(FontStyle.italic),
+ fontSynthesis = eq(FontSynthesis.all)
+ )
+
+ val typeface = paragraph.textPaint.typeface
+ assertThat(typeface, not(nullValue()))
+ assertThat(typeface.isBold, equalTo(false))
+ assertThat(typeface.isItalic, equalTo(true))
+ }
+
+ @Test
+ fun testFontFamily_withGenericFamilyName() {
+ val typefaceAdapter = spy(TypefaceAdapter())
+ val fontFamily = FontFamily("sans-serif")
+
+ val paragraph = simpleParagraph(
+ text = "abc",
+ fontFamily = fontFamily,
+ typefaceAdapter = typefaceAdapter
+ )
+ paragraph.layout(Float.MAX_VALUE)
+
+ verify(typefaceAdapter, times(1)).create(
+ fontFamily = eq(fontFamily),
+ fontWeight = eq(FontWeight.normal),
+ fontStyle = eq(FontStyle.normal),
+ fontSynthesis = eq(FontSynthesis.all)
+ )
+
+ val typeface = paragraph.textPaint.typeface
+ assertThat(typeface, not(nullValue()))
+ assertThat(typeface.isBold, equalTo(false))
+ assertThat(typeface.isItalic, equalTo(false))
+ }
+
+ @Test
+ fun testFontFamily_withCustomFont() {
+ val typefaceAdapter = spy(TypefaceAdapter())
+ val paragraph = simpleParagraph(
+ text = "abc",
+ fontFamily = fontFamily,
+ typefaceAdapter = typefaceAdapter
+ )
+ paragraph.layout(Float.MAX_VALUE)
+
+ verify(typefaceAdapter, times(1)).create(
+ fontFamily = eq(fontFamily),
+ fontWeight = eq(FontWeight.normal),
+ fontStyle = eq(FontStyle.normal),
+ fontSynthesis = eq(FontSynthesis.all)
+ )
+ val typeface = paragraph.textPaint.typeface
+ assertThat(typeface.isBold, equalTo(false))
+ assertThat(typeface.isItalic, equalTo(false))
+ }
+
+ @Test
+ fun testEllipsis_withMaxLineEqualsNull_doesNotEllipsis() {
+ val text = "abc"
+ val fontSize = 20f
+ val paragraphWidth = (text.length - 1) * fontSize
+ val paragraph = simpleParagraph(
+ text = text,
+ fontFamily = fontFamily,
+ fontSize = fontSize,
+ ellipsis = true
+ )
+ paragraph.layout(paragraphWidth)
+ for (i in 0 until paragraph.lineCount) {
+ assertFalse(paragraph.isEllipsisApplied(i))
+ }
+ }
+
+ @Test
+ fun testEllipsis_withMaxLinesLessThanTextLines_doesEllipsis() {
+ val text = "abcde"
+ val fontSize = 100f
+ // Note that on API 21, if the next line only contains 1 character, ellipsis won't work
+ val paragraphWidth = (text.length - 1.5f) * fontSize
+ val paragraph = simpleParagraph(
+ text = text,
+ fontFamily = fontFamily,
+ fontSize = fontSize,
+ ellipsis = true,
+ maxLines = 1
+ )
+ paragraph.layout(paragraphWidth)
+
+ assertTrue(paragraph.isEllipsisApplied(0))
+ }
+
+ @Test
+ fun testEllipsis_withMaxLinesMoreThanTextLines_doesNotEllipsis() {
+ val text = "abc"
+ val fontSize = 100f
+ val paragraphWidth = (text.length - 1) * fontSize
+ val maxLines = ceil(text.length * fontSize / paragraphWidth).toInt()
+ val paragraph = simpleParagraph(
+ text = text,
+ fontFamily = fontFamily,
+ fontSize = fontSize,
+ ellipsis = true,
+ maxLines = maxLines
+ )
+ paragraph.layout(paragraphWidth)
+
+ for (i in 0 until paragraph.lineCount) {
+ assertFalse(paragraph.isEllipsisApplied(i))
+ }
+ }
+
+ private fun simpleParagraph(
+ text: CharSequence = "",
+ textStyles: List<ParagraphBuilder.TextStyleIndex> = listOf(),
+ textAlign: TextAlign? = null,
+ fontSize: Float? = null,
+ ellipsis: Boolean? = null,
+ maxLines: Int? = null,
+ fontFamily: FontFamily? = null,
+ fontWeight: FontWeight? = null,
+ fontStyle: FontStyle? = null,
+ typefaceAdapter: TypefaceAdapter = TypefaceAdapter()
+ ): ParagraphAndroid {
+ return ParagraphAndroid(
+ text = StringBuilder(text),
+ textStyles = textStyles,
+ typefaceAdapter = typefaceAdapter,
+ paragraphStyle = ParagraphStyle(
+ textAlign = textAlign,
+ ellipsis = ellipsis,
+ maxLines = maxLines,
+ fontFamily = fontFamily,
+ fontSize = fontSize,
+ fontWeight = fontWeight,
+ fontStyle = fontStyle
+ )
+ )
+ }
+}