[go: nahoru, domu]

blob: 4f2e661ce9432565dd113cb5da00ae1bb02ca62a [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.ui.text
import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.ui.intl.LocaleList
import androidx.ui.geometry.Rect
import androidx.ui.graphics.Canvas
import androidx.ui.graphics.Color
import androidx.ui.graphics.ImageAsset
import androidx.ui.graphics.ImageAssetConfig
import androidx.ui.graphics.Path
import androidx.ui.graphics.PathOperation
import androidx.ui.graphics.Shadow
import androidx.ui.graphics.asAndroidBitmap
import androidx.ui.text.FontTestData.Companion.BASIC_KERN_FONT
import androidx.ui.text.FontTestData.Companion.BASIC_MEASURE_FONT
import androidx.ui.text.FontTestData.Companion.FONT_100_REGULAR
import androidx.ui.text.FontTestData.Companion.FONT_200_REGULAR
import androidx.ui.text.font.asFontFamily
import androidx.ui.text.matchers.assertThat
import androidx.ui.text.matchers.isZero
import androidx.ui.text.style.TextAlign
import androidx.ui.text.style.ResolvedTextDirection
import androidx.ui.text.style.TextDirection
import androidx.ui.text.style.TextGeometricTransform
import androidx.ui.text.style.TextIndent
import androidx.ui.unit.Density
import androidx.ui.geometry.Offset
import androidx.ui.unit.em
import androidx.ui.unit.sp
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import kotlin.math.roundToInt
@RunWith(JUnit4::class)
@SmallTest
class ParagraphIntegrationTest {
private val fontFamilyMeasureFont = BASIC_MEASURE_FONT.asFontFamily()
private val fontFamilyKernFont = BASIC_KERN_FONT.asFontFamily()
private val fontFamilyCustom100 = FONT_100_REGULAR.asFontFamily()
private val fontFamilyCustom200 = FONT_200_REGULAR.asFontFamily()
private val context = InstrumentationRegistry.getInstrumentation().context
private val defaultDensity = Density(density = 1f)
private val ltrLocaleList = LocaleList("en")
private val resourceLoader = TestFontResourceLoader(context)
private val cursorWidth = 4f
@Test
fun empty_string() {
with(defaultDensity) {
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val text = ""
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = 100.0f)
)
assertThat(paragraph.width).isEqualTo(100.0f)
assertThat(paragraph.height).isEqualTo(fontSizeInPx)
// defined in sample_font
assertThat(paragraph.firstBaseline).isEqualTo(fontSizeInPx * 0.8f)
assertThat(paragraph.lastBaseline).isEqualTo(fontSizeInPx * 0.8f)
assertThat(paragraph.maxIntrinsicWidth).isZero()
assertThat(paragraph.minIntrinsicWidth).isZero()
}
}
@Test
fun single_line_default_values() {
with(defaultDensity) {
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
for (text in arrayOf("xyz", "\u05D0\u05D1\u05D2")) {
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
// width greater than text width - 150
constraints = ParagraphConstraints(width = 200.0f)
)
assertWithMessage(text).that(paragraph.width).isEqualTo(200.0f)
assertWithMessage(text).that(paragraph.height).isEqualTo(fontSizeInPx)
// defined in sample_font
assertWithMessage(text).that(paragraph.firstBaseline).isEqualTo(fontSizeInPx * 0.8f)
assertWithMessage(text).that(paragraph.lastBaseline).isEqualTo(fontSizeInPx * 0.8f)
assertWithMessage(text).that(paragraph.maxIntrinsicWidth)
.isEqualTo(fontSizeInPx * text.length)
assertWithMessage(text).that(paragraph.minIntrinsicWidth)
.isEqualTo(text.length * fontSizeInPx)
}
}
}
@Test
fun line_break_default_values() {
with(defaultDensity) {
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
for (text in arrayOf("abcdef", "\u05D0\u05D1\u05D2\u05D3\u05D4\u05D5")) {
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
// 3 chars width
constraints = ParagraphConstraints(width = 3 * fontSizeInPx)
)
// 3 chars
assertWithMessage(text).that(paragraph.width)
.isEqualTo(3 * fontSizeInPx)
// 2 lines, 1 line gap
assertWithMessage(text).that(paragraph.height)
.isEqualTo(2 * fontSizeInPx + fontSizeInPx / 5.0f)
// defined in sample_font
assertWithMessage(text).that(paragraph.firstBaseline)
.isEqualTo(fontSizeInPx * 0.8f)
assertWithMessage(text).that(paragraph.lastBaseline)
.isEqualTo(fontSizeInPx + fontSizeInPx / 5.0f + fontSizeInPx * 0.8f)
assertWithMessage(text).that(paragraph.maxIntrinsicWidth)
.isEqualTo(fontSizeInPx * text.length)
assertWithMessage(text).that(paragraph.minIntrinsicWidth)
.isEqualTo(text.length * fontSizeInPx)
}
}
}
@Test
fun newline_default_values() {
with(defaultDensity) {
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
for (text in arrayOf("abc\ndef", "\u05D0\u05D1\u05D2\n\u05D3\u05D4\u05D5")) {
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
// 3 chars width
constraints = ParagraphConstraints(width = 3 * fontSizeInPx)
)
// 3 chars
assertWithMessage(text).that(paragraph.width).isEqualTo(3 * fontSizeInPx)
// 2 lines, 1 line gap
assertWithMessage(text).that(paragraph.height)
.isEqualTo(2 * fontSizeInPx + fontSizeInPx / 5.0f)
// defined in sample_font
assertWithMessage(text).that(paragraph.firstBaseline).isEqualTo(fontSizeInPx * 0.8f)
assertWithMessage(text).that(paragraph.lastBaseline)
.isEqualTo(fontSizeInPx + fontSizeInPx / 5.0f + fontSizeInPx * 0.8f)
assertWithMessage(text).that(paragraph.maxIntrinsicWidth)
.isEqualTo(fontSizeInPx * text.indexOf("\n"))
assertWithMessage(text).that(paragraph.minIntrinsicWidth)
.isEqualTo(fontSizeInPx * text.indexOf("\n"))
}
}
}
@Test
fun newline_and_line_break_default_values() {
with(defaultDensity) {
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
for (text in arrayOf("abc\ndef", "\u05D0\u05D1\u05D2\n\u05D3\u05D4\u05D5")) {
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
// 2 chars width
constraints = ParagraphConstraints(width = 2 * fontSizeInPx)
)
// 2 chars
assertWithMessage(text).that(paragraph.width).isEqualTo(2 * fontSizeInPx)
// 4 lines, 3 line gaps
assertWithMessage(text).that(paragraph.height)
.isEqualTo(4 * fontSizeInPx + 3 * fontSizeInPx / 5.0f)
// defined in sample_font
assertWithMessage(text).that(paragraph.firstBaseline)
.isEqualTo(fontSizeInPx * 0.8f)
assertWithMessage(text).that(paragraph.lastBaseline)
.isEqualTo(3 * fontSizeInPx + 3 * fontSizeInPx / 5.0f + fontSizeInPx * 0.8f)
assertWithMessage(text).that(paragraph.maxIntrinsicWidth)
.isEqualTo(fontSizeInPx * text.indexOf("\n"))
assertWithMessage(text).that(paragraph.minIntrinsicWidth)
.isEqualTo(fontSizeInPx * text.indexOf("\n"))
}
}
}
@Test
fun getOffsetForPosition_ltr() {
with(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
)
// test positions that are 1, fontSize+1, 2fontSize+1 which maps to chars 0, 1, 2 ...
for (i in 0..text.length) {
val position = Offset((i * fontSizeInPx + 1), (fontSizeInPx / 2))
val offset = paragraph.getOffsetForPosition(position)
assertWithMessage("offset at index $i, position $position does not match")
.that(offset).isEqualTo(i)
}
}
}
@Test
fun getOffsetForPosition_rtl() {
with(defaultDensity) {
val text = "\u05D0\u05D1\u05D2"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
)
// test positions that are 1, fontSize+1, 2fontSize+1 which maps to chars .., 2, 1, 0
for (i in 0..text.length) {
val position = Offset((i * fontSizeInPx + 1), (fontSizeInPx / 2))
val offset = paragraph.getOffsetForPosition(position)
assertWithMessage("offset at index $i, position $position does not match")
.that(offset).isEqualTo(text.length - i)
}
}
}
@Test
fun getOffsetForPosition_ltr_multiline() {
with(defaultDensity) {
val firstLine = "abc"
val secondLine = "def"
val text = firstLine + secondLine
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = firstLine.length * fontSizeInPx)
)
// test positions are 1, fontSize+1, 2fontSize+1 and always on the second line
// which maps to chars 3, 4, 5
for (i in 0..secondLine.length) {
val position = Offset((i * fontSizeInPx + 1), (fontSizeInPx * 1.5f))
val offset = paragraph.getOffsetForPosition(position)
assertWithMessage(
"offset at index $i, position $position, second line does not match"
).that(offset).isEqualTo(i + firstLine.length)
}
}
}
@Test
fun getOffsetForPosition_rtl_multiline() {
with(defaultDensity) {
val firstLine = "\u05D0\u05D1\u05D2"
val secondLine = "\u05D3\u05D4\u05D5"
val text = firstLine + secondLine
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = firstLine.length * fontSizeInPx)
)
// test positions are 1, fontSize+1, 2fontSize+1 and always on the second line
// which maps to chars 5, 4, 3
for (i in 0..secondLine.length) {
val position = Offset((i * fontSizeInPx + 1), (fontSizeInPx * 1.5f))
val offset = paragraph.getOffsetForPosition(position)
assertWithMessage(
"offset at index $i, position $position, second line does not match"
).that(offset).isEqualTo(text.length - i)
}
}
}
@Test
fun getOffsetForPosition_ltr_width_outOfBounds() {
with(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
)
// greater than width
var position = Offset((fontSizeInPx * text.length * 2), (fontSizeInPx / 2))
var offset = paragraph.getOffsetForPosition(position)
assertThat(offset).isEqualTo(text.length)
// negative
position = Offset((-1 * fontSizeInPx), (fontSizeInPx / 2))
offset = paragraph.getOffsetForPosition(position)
assertThat(offset).isZero()
}
}
@Test
fun getOffsetForPosition_ltr_height_outOfBounds() {
with(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
)
// greater than height
var position = Offset((fontSizeInPx / 2), (fontSizeInPx * text.length * 2))
var offset = paragraph.getOffsetForPosition(position)
assertThat(offset).isZero()
// negative
position = Offset((fontSizeInPx / 2), (-1 * fontSizeInPx))
offset = paragraph.getOffsetForPosition(position)
assertThat(offset).isZero()
}
}
@Test
fun getBoundingBox_ltr_singleLine() {
with(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
)
// test positions that are 0, 1, 2 ... which maps to chars 0, 1, 2 ...
for (i in 0..text.length - 1) {
val box = paragraph.getBoundingBox(i)
assertThat(box.left).isEqualTo(i * fontSizeInPx)
assertThat(box.right).isEqualTo((i + 1) * fontSizeInPx)
assertThat(box.top).isZero()
assertThat(box.bottom).isEqualTo(fontSizeInPx)
}
}
}
@Test
fun getBoundingBox_ltr_multiLines() {
with(defaultDensity) {
val firstLine = "abc"
val secondLine = "def"
val text = firstLine + secondLine
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = firstLine.length * fontSizeInPx)
)
// test positions are 3, 4, 5 and always on the second line
// which maps to chars 3, 4, 5
for (i in secondLine.indices) {
val textPosition = i + firstLine.length
val box = paragraph.getBoundingBox(textPosition)
assertThat(box.left).isEqualTo(i * fontSizeInPx)
assertThat(box.right).isEqualTo((i + 1) * fontSizeInPx)
assertThat(box.top).isEqualTo(fontSizeInPx)
assertThat(box.bottom).isEqualTo((2f + 1 / 5f) * fontSizeInPx)
}
}
}
@Test
fun getBoundingBox_ltr_textPosition_negative() {
with(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
)
val textPosition = -1
val box = paragraph.getBoundingBox(textPosition)
assertThat(box.left).isZero()
assertThat(box.right).isZero()
assertThat(box.top).isZero()
assertThat(box.bottom).isEqualTo(fontSizeInPx)
}
}
@Test(expected = java.lang.IndexOutOfBoundsException::class)
@SdkSuppress(minSdkVersion = 26)
fun getBoundingBox_ltr_textPosition_larger_than_length_throw_exception() {
with(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
)
val textPosition = text.length + 1
paragraph.getBoundingBox(textPosition)
}
}
@Test(expected = java.lang.AssertionError::class)
fun getCursorRect_larger_than_length_throw_exception() {
with(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
)
paragraph.getCursorRect(text.length + 1)
}
}
@Test(expected = java.lang.AssertionError::class)
fun getCursorRect_negative_throw_exception() {
with(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
)
paragraph.getCursorRect(-1)
}
}
@Test
fun getCursorRect_ltr_singleLine() {
with(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
)
for (i in text.indices) {
val cursorRect = paragraph.getCursorRect(i)
val cursorXOffset = i * fontSizeInPx
assertThat(cursorRect).isEqualTo(
Rect(
left = cursorXOffset - cursorWidth / 2,
top = 0f,
right = cursorXOffset + cursorWidth / 2,
bottom = fontSizeInPx
)
)
}
}
}
@Test
fun getCursorRect_ltr_multiLines() {
with(defaultDensity) {
val text = "abcdef"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val charsPerLine = 3
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = charsPerLine * fontSizeInPx)
)
for (i in 0 until charsPerLine) {
val cursorXOffset = i * fontSizeInPx
assertThat(paragraph.getCursorRect(i)).isEqualTo(
Rect(
left = cursorXOffset - cursorWidth / 2,
top = 0f,
right = cursorXOffset + cursorWidth / 2,
bottom = fontSizeInPx
)
)
}
for (i in charsPerLine until text.length) {
val cursorXOffset = (i % charsPerLine) * fontSizeInPx
assertThat(paragraph.getCursorRect(i)).isEqualTo(
Rect(
left = cursorXOffset - cursorWidth / 2,
top = fontSizeInPx,
right = cursorXOffset + cursorWidth / 2,
bottom = fontSizeInPx * 2.2f
)
)
}
}
}
@Test
fun getCursorRect_ltr_newLine() {
with(defaultDensity) {
val text = "abc\ndef"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize)
)
// Cursor before '\n'
assertThat(paragraph.getCursorRect(3)).isEqualTo(
Rect(
left = 3 * fontSizeInPx - cursorWidth / 2,
top = 0f,
right = 3 * fontSizeInPx + cursorWidth / 2,
bottom = fontSizeInPx
)
)
// Cursor after '\n'
assertThat(paragraph.getCursorRect(4)).isEqualTo(
Rect(
left = -cursorWidth / 2,
top = fontSizeInPx,
right = cursorWidth / 2,
bottom = fontSizeInPx * 2.2f
)
)
}
}
@Test
fun getCursorRect_ltr_newLine_last_char() {
with(defaultDensity) {
val text = "abc\n"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize, localeList = ltrLocaleList)
)
// Cursor before '\n'
assertThat(paragraph.getCursorRect(3)).isEqualTo(
Rect(
left = 3 * fontSizeInPx - cursorWidth / 2,
top = 0f,
right = 3 * fontSizeInPx + cursorWidth / 2,
bottom = fontSizeInPx
)
)
// Cursor after '\n'
assertThat(paragraph.getCursorRect(4)).isEqualTo(
Rect(
left = -cursorWidth / 2,
top = fontSizeInPx,
right = cursorWidth / 2,
bottom = fontSizeInPx * 2.2f
)
)
}
}
@Test
fun getCursorRect_rtl_singleLine() {
with(defaultDensity) {
val text = "\u05D0\u05D1\u05D2"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
)
for (i in text.indices) {
val cursorXOffset = (text.length - i) * fontSizeInPx
assertThat(paragraph.getCursorRect(i)).isEqualTo(
Rect(
left = cursorXOffset - cursorWidth / 2,
top = 0f,
right = cursorXOffset + cursorWidth / 2,
bottom = fontSizeInPx
)
)
}
}
}
@Test
fun getCursorRect_rtl_multiLines() {
with(defaultDensity) {
val text = "\u05D0\u05D1\u05D2\u05D0\u05D1\u05D2"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val charsPerLine = 3
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = charsPerLine * fontSizeInPx)
)
for (i in 0 until charsPerLine) {
val cursorXOffset = (charsPerLine - i) * fontSizeInPx
assertThat(paragraph.getCursorRect(i)).isEqualTo(
Rect(
left = cursorXOffset - cursorWidth / 2,
top = 0f,
right = cursorXOffset + cursorWidth / 2,
bottom = fontSizeInPx
)
)
}
for (i in charsPerLine until text.length) {
val cursorXOffset = (charsPerLine - i % charsPerLine) * fontSizeInPx
assertThat(paragraph.getCursorRect(i)).isEqualTo(
Rect(
left = cursorXOffset - cursorWidth / 2,
top = fontSizeInPx,
right = cursorXOffset + cursorWidth / 2,
bottom = fontSizeInPx * 2.2f
)
)
}
}
}
@Test
fun getCursorRect_rtl_newLine() {
with(defaultDensity) {
val text = "\u05D0\u05D1\u05D2\n\u05D0\u05D1\u05D2"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = 3 * fontSizeInPx)
)
// Cursor before '\n'
assertThat(paragraph.getCursorRect(3)).isEqualTo(
Rect(
left = 0 - cursorWidth / 2,
top = 0f,
right = 0 + cursorWidth / 2,
bottom = fontSizeInPx
)
)
// Cursor after '\n'
assertThat(paragraph.getCursorRect(4)).isEqualTo(
Rect(
left = 3 * fontSizeInPx - cursorWidth / 2,
top = fontSizeInPx,
right = 3 * fontSizeInPx + cursorWidth / 2,
bottom = fontSizeInPx * 2.2f
)
)
}
}
@Test
@SdkSuppress(minSdkVersion = 23)
fun getCursorRect_rtl_newLine_last_char() {
with(defaultDensity) {
val text = "\u05D0\u05D1\u05D2\n"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize, localeList = ltrLocaleList),
constraints = ParagraphConstraints(width = 3 * fontSizeInPx)
)
// Cursor before '\n'
assertThat(paragraph.getCursorRect(3)).isEqualTo(
Rect(
left = 0 - cursorWidth / 2,
top = 0f,
right = 0 + cursorWidth / 2,
bottom = fontSizeInPx
)
)
// Cursor after '\n'
assertThat(paragraph.getCursorRect(4)).isEqualTo(
Rect(
left = -cursorWidth / 2,
top = fontSizeInPx,
right = +cursorWidth / 2,
bottom = fontSizeInPx * 2.2f
)
)
}
}
@Test
fun getHorizontalPositionForOffset_primary_ltr_singleLine_textDirectionDefault() {
with(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
)
for (i in 0..text.length) {
assertThat(paragraph.getHorizontalPosition(i, true))
.isEqualTo(fontSizeInPx * i)
}
}
}
@Test
fun getHorizontalPositionForOffset_primary_rtl_singleLine_textDirectionDefault() {
with(defaultDensity) {
val text = "\u05D0\u05D1\u05D2"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width)
)
for (i in 0..text.length) {
assertThat(paragraph.getHorizontalPosition(i, true))
.isEqualTo(width - fontSizeInPx * i)
}
}
}
@Test
fun getHorizontalPositionForOffset_primary_Bidi_singleLine_textDirectionDefault() {
with(defaultDensity) {
val ltrText = "abc"
val rtlText = "\u05D0\u05D1\u05D2"
val text = ltrText + rtlText
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width)
)
for (i in 0..ltrText.length) {
assertThat(paragraph.getHorizontalPosition(i, true))
.isEqualTo(fontSizeInPx * i)
}
for (i in 1 until rtlText.length) {
assertThat(paragraph.getHorizontalPosition(i + ltrText.length, true))
.isEqualTo(width - fontSizeInPx * i)
}
assertThat(paragraph.getHorizontalPosition(text.length, true))
.isEqualTo(width)
}
}
@Test
fun getHorizontalPositionForOffset_primary_ltr_singleLine_textDirectionRtl() {
with(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Rtl
),
constraints = ParagraphConstraints(width)
)
assertThat(paragraph.getHorizontalPosition(0, true)).isEqualTo(width)
for (i in 1 until text.length) {
assertThat(paragraph.getHorizontalPosition(i, true))
.isEqualTo(fontSizeInPx * i)
}
assertThat(paragraph.getHorizontalPosition(text.length, true)).isZero()
}
}
@Test
fun getHorizontalPositionForOffset_primary_rtl_singleLine_textDirectionLtr() {
with(defaultDensity) {
val text = "\u05D0\u05D1\u05D2"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Ltr
),
constraints = ParagraphConstraints(width)
)
assertThat(paragraph.getHorizontalPosition(0, true)).isZero()
for (i in 1 until text.length) {
assertThat(paragraph.getHorizontalPosition(i, true))
.isEqualTo(width - fontSizeInPx * i)
}
assertThat(paragraph.getHorizontalPosition(text.length, true))
.isEqualTo(width)
}
}
@Test
fun getHorizontalPositionForOffset_primary_Bidi_singleLine_textDirectionLtr() {
with(defaultDensity) {
val ltrText = "abc"
val rtlText = "\u05D0\u05D1\u05D2"
val text = ltrText + rtlText
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Ltr
),
constraints = ParagraphConstraints(width)
)
for (i in 0..ltrText.length) {
assertThat(paragraph.getHorizontalPosition(i, true))
.isEqualTo(fontSizeInPx * i)
}
for (i in 1 until rtlText.length) {
assertThat(paragraph.getHorizontalPosition(i + ltrText.length, true))
.isEqualTo(width - fontSizeInPx * i)
}
assertThat(paragraph.getHorizontalPosition(text.length, true))
.isEqualTo(width)
}
}
@Test
fun getHorizontalPositionForOffset_primary_Bidi_singleLine_textDirectionRtl() {
with(defaultDensity) {
val ltrText = "abc"
val rtlText = "\u05D0\u05D1\u05D2"
val text = ltrText + rtlText
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Rtl
),
constraints = ParagraphConstraints(width)
)
assertThat(paragraph.getHorizontalPosition(0, true)).isEqualTo(width)
for (i in 1 until ltrText.length) {
assertThat(paragraph.getHorizontalPosition(i, true))
.isEqualTo(rtlText.length * fontSizeInPx + i * fontSizeInPx)
}
for (i in 0..rtlText.length) {
assertThat(paragraph.getHorizontalPosition(i + ltrText.length, true))
.isEqualTo(rtlText.length * fontSizeInPx - i * fontSizeInPx)
}
}
}
@Test
fun getHorizontalPositionForOffset_primary_ltr_newLine_textDirectionDefault() {
with(defaultDensity) {
val text = "abc\n"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize, localeList = ltrLocaleList),
constraints = ParagraphConstraints(width)
)
assertThat(paragraph.getHorizontalPosition(text.length, true)).isZero()
}
}
@Test
// The behavior of getPrimaryHorizontal on API 19 to API 22 was wrong. Suppress this test.
@SdkSuppress(minSdkVersion = 23)
fun getHorizontalPositionForOffset_primary_rtl_newLine_textDirectionDefault() {
with(defaultDensity) {
val text = "\u05D0\u05D1\u05D2\n"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize, localeList = ltrLocaleList),
constraints = ParagraphConstraints(width)
)
assertThat(paragraph.getHorizontalPosition(text.length, true)).isZero()
}
}
@Test
fun getHorizontalPositionForOffset_primary_ltr_newLine_textDirectionRtl() {
with(defaultDensity) {
val text = "abc\n"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Rtl
),
constraints = ParagraphConstraints(width)
)
assertThat(paragraph.getHorizontalPosition(text.length, true))
.isEqualTo(width)
}
}
@Test
fun getHorizontalPositionForOffset_primary_rtl_newLine_textDirectionLtr() {
with(defaultDensity) {
val text = "\u05D0\u05D1\u05D2\n"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Ltr
),
constraints = ParagraphConstraints(width)
)
assertThat(paragraph.getHorizontalPosition(text.length, true)).isZero()
}
}
@Test
fun getHorizontalPositionForOffset_notPrimary_ltr_singleLine_textDirectionDefault() {
with(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
)
for (i in 0..text.length) {
assertThat(paragraph.getHorizontalPosition(i, false))
.isEqualTo(fontSizeInPx * i)
}
}
}
@Test
fun getHorizontalPositionForOffset_notPrimary_rtl_singleLine_textDirectionDefault() {
with(defaultDensity) {
val text = "\u05D0\u05D1\u05D2"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width)
)
for (i in 0..text.length) {
assertThat(paragraph.getHorizontalPosition(i, false))
.isEqualTo(width - fontSizeInPx * i)
}
}
}
@Test
fun getHorizontalPositionForOffset_notPrimary_Bidi_singleLine_textDirectionDefault() {
with(defaultDensity) {
val ltrText = "abc"
val rtlText = "\u05D0\u05D1\u05D2"
val text = ltrText + rtlText
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width)
)
for (i in ltrText.indices) {
assertThat(paragraph.getHorizontalPosition(i, false))
.isEqualTo(fontSizeInPx * i)
}
for (i in 0..rtlText.length) {
assertThat(paragraph.getHorizontalPosition(i + ltrText.length, false))
.isEqualTo(width - fontSizeInPx * i)
}
}
}
@Test
fun getHorizontalPositionForOffset_notPrimary_ltr_singleLine_textDirectionRtl() {
with(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Rtl
),
constraints = ParagraphConstraints(width)
)
assertThat(paragraph.getHorizontalPosition(0, false)).isZero()
for (i in 1 until text.length) {
assertThat(paragraph.getHorizontalPosition(i, false))
.isEqualTo(fontSizeInPx * i)
}
assertThat(paragraph.getHorizontalPosition(text.length, false))
.isEqualTo(width)
}
}
@Test
fun getHorizontalPositionForOffset_notPrimary_rtl_singleLine_textDirectionLtr() {
with(defaultDensity) {
val text = "\u05D0\u05D1\u05D2"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Ltr
),
constraints = ParagraphConstraints(width)
)
assertThat(paragraph.getHorizontalPosition(0, false)).isEqualTo(width)
for (i in 1 until text.length) {
assertThat(paragraph.getHorizontalPosition(i, false))
.isEqualTo(width - fontSizeInPx * i)
}
assertThat(paragraph.getHorizontalPosition(text.length, false)).isZero()
}
}
@Test
fun getHorizontalPositionForOffset_notPrimary_Bidi_singleLine_textDirectionLtr() {
with(defaultDensity) {
val ltrText = "abc"
val rtlText = "\u05D0\u05D1\u05D2"
val text = ltrText + rtlText
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Ltr
),
constraints = ParagraphConstraints(width)
)
for (i in ltrText.indices) {
assertThat(paragraph.getHorizontalPosition(i, false))
.isEqualTo(fontSizeInPx * i)
}
for (i in rtlText.indices) {
assertThat(paragraph.getHorizontalPosition(i + ltrText.length, false))
.isEqualTo(width - fontSizeInPx * i)
}
assertThat(paragraph.getHorizontalPosition(text.length, false))
.isEqualTo(width - rtlText.length * fontSizeInPx)
}
}
@Test
fun getHorizontalPositionForOffset_notPrimary_Bidi_singleLine_textDirectionRtl() {
with(defaultDensity) {
val ltrText = "abc"
val rtlText = "\u05D0\u05D1\u05D2"
val text = ltrText + rtlText
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Rtl
),
constraints = ParagraphConstraints(width)
)
assertThat(paragraph.getHorizontalPosition(0, false))
.isEqualTo(width - ltrText.length * fontSizeInPx)
for (i in 1..ltrText.length) {
assertThat(paragraph.getHorizontalPosition(i, false))
.isEqualTo(rtlText.length * fontSizeInPx + i * fontSizeInPx)
}
for (i in 1..rtlText.length) {
assertThat(paragraph.getHorizontalPosition(i + ltrText.length, false))
.isEqualTo(rtlText.length * fontSizeInPx - i * fontSizeInPx)
}
}
}
@Test
fun getHorizontalPositionForOffset_notPrimary_ltr_newLine_textDirectionDefault() {
with(defaultDensity) {
val text = "abc\n"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize, localeList = ltrLocaleList),
constraints = ParagraphConstraints(width)
)
assertThat(paragraph.getHorizontalPosition(text.length, false)).isZero()
}
}
@Test
@SdkSuppress(minSdkVersion = 23)
// The behavior of getSecondaryHorizontal on API 19 to API 22 was wrong. Suppress this test.
fun getHorizontalPositionForOffset_notPrimary_rtl_newLine_textDirectionDefault() {
with(defaultDensity) {
val text = "\u05D0\u05D1\u05D2\n"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize, localeList = ltrLocaleList),
constraints = ParagraphConstraints(width)
)
assertThat(paragraph.getHorizontalPosition(text.length, false)).isZero()
}
}
@Test
fun getHorizontalPositionForOffset_notPrimary_ltr_newLine_textDirectionRtl() {
with(defaultDensity) {
val text = "abc\n"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Rtl
),
constraints = ParagraphConstraints(width)
)
assertThat(paragraph.getHorizontalPosition(text.length, false))
.isEqualTo(width)
}
}
@Test
fun getHorizontalPositionForOffset_notPrimary_rtl_newLine_textDirectionLtr() {
with(defaultDensity) {
val text = "\u05D0\u05D1\u05D2\n"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Ltr
),
constraints = ParagraphConstraints(width)
)
assertThat(paragraph.getHorizontalPosition(text.length, false)).isZero()
}
}
@Test
fun getParagraphDirection_ltr_singleLine_textDirectionDefault() {
with(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width)
)
for (i in 0..text.length) {
assertThat(paragraph.getParagraphDirection(i)).isEqualTo(ResolvedTextDirection.Ltr)
}
}
}
@Test
fun getParagraphDirection_ltr_singleLine_textDirectionRtl() {
with(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Rtl
),
constraints = ParagraphConstraints(width)
)
for (i in 0..text.length) {
assertThat(paragraph.getParagraphDirection(i)).isEqualTo(ResolvedTextDirection.Rtl)
}
}
}
@Test
fun getParagraphDirection_rtl_singleLine_textDirectionDefault() {
with(defaultDensity) {
val text = "\u05D0\u05D1\u05D2\n"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width)
)
for (i in text.indices) {
assertThat(paragraph.getParagraphDirection(i)).isEqualTo(ResolvedTextDirection.Rtl)
}
}
}
@Test
fun getParagraphDirection_rtl_singleLine_textDirectionLtr() {
with(defaultDensity) {
val text = "\u05D0\u05D1\u05D2\n"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Ltr
),
constraints = ParagraphConstraints(width)
)
for (i in 0..text.length) {
assertThat(paragraph.getParagraphDirection(i)).isEqualTo(ResolvedTextDirection.Ltr)
}
}
}
@Test
fun getParagraphDirection_Bidi_singleLine_textDirectionDefault() {
with(defaultDensity) {
val ltrText = "abc"
val rtlText = "\u05D0\u05D1\u05D2"
val text = ltrText + rtlText
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width)
)
for (i in 0..text.length) {
assertThat(paragraph.getParagraphDirection(i)).isEqualTo(ResolvedTextDirection.Ltr)
}
}
}
@Test
fun getParagraphDirection_Bidi_singleLine_textDirectionLtr() {
with(defaultDensity) {
val ltrText = "abc"
val rtlText = "\u05D0\u05D1\u05D2"
val text = ltrText + rtlText
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Ltr
),
constraints = ParagraphConstraints(width)
)
for (i in 0..text.length) {
assertThat(paragraph.getParagraphDirection(i)).isEqualTo(ResolvedTextDirection.Ltr)
}
}
}
@Test
fun getParagraphDirection_Bidi_singleLine_textDirectionRtl() {
with(defaultDensity) {
val ltrText = "abc"
val rtlText = "\u05D0\u05D1\u05D2"
val text = ltrText + rtlText
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Rtl
),
constraints = ParagraphConstraints(width)
)
for (i in 0..text.length) {
assertThat(paragraph.getParagraphDirection(i)).isEqualTo(ResolvedTextDirection.Rtl)
}
}
}
@Test
fun getBidiRunDirection_ltr_singleLine_textDirectionDefault() {
with(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width)
)
for (i in 0..text.length) {
assertThat(paragraph.getBidiRunDirection(i)).isEqualTo(ResolvedTextDirection.Ltr)
}
}
}
@Test
fun getBidiRunDirection_ltr_singleLine_textDirectionRtl() {
with(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Rtl
),
constraints = ParagraphConstraints(width)
)
for (i in 0..text.length) {
assertThat(paragraph.getBidiRunDirection(i)).isEqualTo(ResolvedTextDirection.Ltr)
}
}
}
@Test
fun getBidiRunDirection_rtl_singleLine_textDirectionDefault() {
with(defaultDensity) {
val text = "\u05D0\u05D1\u05D2\n"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width)
)
for (i in text.indices) {
assertThat(paragraph.getBidiRunDirection(i)).isEqualTo(ResolvedTextDirection.Rtl)
}
}
}
@Test
fun getBidiRunDirection_rtl_singleLine_textDirectionLtr() {
with(defaultDensity) {
val text = "\u05D0\u05D1\u05D2\n"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Ltr
),
constraints = ParagraphConstraints(width)
)
for (i in 0 until text.length - 1) {
assertThat(paragraph.getBidiRunDirection(i)).isEqualTo(ResolvedTextDirection.Rtl)
}
assertThat(
paragraph.getBidiRunDirection(text.length - 1)
).isEqualTo(ResolvedTextDirection.Ltr)
}
}
@Test
fun getBidiRunDirection_Bidi_singleLine_textDirectionDefault() {
with(defaultDensity) {
val ltrText = "abc"
val rtlText = "\u05D0\u05D1\u05D2"
val text = ltrText + rtlText
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width)
)
for (i in ltrText.indices) {
assertThat(paragraph.getBidiRunDirection(i)).isEqualTo(ResolvedTextDirection.Ltr)
}
for (i in ltrText.length until text.length) {
assertThat(paragraph.getBidiRunDirection(i)).isEqualTo(ResolvedTextDirection.Rtl)
}
}
}
@Test
fun getBidiRunDirection_Bidi_singleLine_textDirectionLtr() {
with(defaultDensity) {
val ltrText = "abc"
val rtlText = "\u05D0\u05D1\u05D2"
val text = ltrText + rtlText
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Ltr
),
constraints = ParagraphConstraints(width)
)
for (i in ltrText.indices) {
assertThat(paragraph.getBidiRunDirection(i)).isEqualTo(ResolvedTextDirection.Ltr)
}
for (i in ltrText.length until text.length) {
assertThat(paragraph.getBidiRunDirection(i)).isEqualTo(ResolvedTextDirection.Rtl)
}
}
}
@Test
fun getBidiRunDirection_Bidi_singleLine_textDirectionRtl() {
with(defaultDensity) {
val ltrText = "abc"
val rtlText = "\u05D0\u05D1\u05D2"
val text = ltrText + rtlText
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val width = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Rtl
),
constraints = ParagraphConstraints(width)
)
for (i in ltrText.indices) {
assertThat(paragraph.getBidiRunDirection(i)).isEqualTo(ResolvedTextDirection.Ltr)
}
for (i in ltrText.length until text.length) {
assertThat(paragraph.getBidiRunDirection(i)).isEqualTo(ResolvedTextDirection.Rtl)
}
}
}
@Test
fun locale_withCJK_shouldNotDrawSame() {
with(defaultDensity) {
val text = "\u82B1"
val fontSize = 10.sp
val fontSizeInPx = fontSize.toPx()
val locales = arrayOf(
// duplicate ja is on purpose
LocaleList("ja"),
LocaleList("ja"),
LocaleList("zh-CN"),
LocaleList("zh-TW")
)
val bitmaps = locales.map { localeList ->
val paragraph = Paragraph(
text = text,
spanStyles = listOf(),
style = TextStyle(
fontSize = fontSize,
localeList = localeList
),
density = defaultDensity,
resourceLoader = resourceLoader,
// just have 10x font size to have a bitmap
constraints = ParagraphConstraints(width = fontSizeInPx * 10)
)
paragraph.bitmap()
}
assertThat(bitmaps[0]).isEqualToBitmap(bitmaps[1])
assertThat(bitmaps[1]).isNotEqualToBitmap(bitmaps[2])
assertThat(bitmaps[1]).isNotEqualToBitmap(bitmaps[3])
// this does not work on API 21
// assertThat(bitmaps[2], not(equalToBitmap(bitmaps[3])))
}
}
@Test
fun lineCount_withMaxLineSmallerThanTextLines() {
with(defaultDensity) {
val text = "a\na\na"
val fontSize = 100.sp
val lineCount = text.lines().size
val maxLines = lineCount - 1
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
maxLines = maxLines
)
assertThat(paragraph.lineCount).isEqualTo(maxLines)
}
}
@Test
fun lineCount_withMaxLineGreaterThanTextLines() {
with(defaultDensity) {
val text = "a\na\na"
val fontSize = 100.sp
val lineCount = text.lines().size
val maxLines = lineCount + 1
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
maxLines = maxLines
)
assertThat(paragraph.lineCount).isEqualTo(lineCount)
}
}
@Test(expected = java.lang.IllegalArgumentException::class)
fun maxLines_withMaxLineEqualsZero_throwsException() {
simpleParagraph(
text = "",
maxLines = 0
)
}
@Test(expected = java.lang.IllegalArgumentException::class)
fun maxLines_withMaxLineNegative_throwsException() {
simpleParagraph(
text = "",
maxLines = -1
)
}
@Test
fun maxLines_withMaxLineSmallerThanTextLines_clipHeight() {
with(defaultDensity) {
val text = "a\na\na"
val fontSize = 100.sp
val fontSizeInPx = fontSize.toPx()
val lineCount = text.lines().size
val maxLines = lineCount - 1
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
maxLines = maxLines
)
val expectHeight = (maxLines + (maxLines - 1) * 0.2f) * fontSizeInPx
assertThat(paragraph.height).isEqualTo(expectHeight)
}
}
@Test
fun maxLines_withMaxLineSmallerThanTextLines_haveCorrectBaselines() {
with(defaultDensity) {
val text = "a\na\na"
val fontSize = 100.sp
val fontSizeInPx = fontSize.toPx()
val lineCount = text.lines().size
val maxLines = lineCount - 1
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
maxLines = maxLines
)
val expectFirstBaseline = 0.8f * fontSizeInPx
assertThat(paragraph.firstBaseline).isEqualTo(expectFirstBaseline)
val expectLastBaseline =
((maxLines - 1) + (maxLines - 1) * 0.2f) * fontSizeInPx + 0.8f * fontSizeInPx
assertThat(paragraph.lastBaseline).isEqualTo(expectLastBaseline)
}
}
@Test
fun maxLines_withMaxLineEqualsTextLine() {
with(defaultDensity) {
val text = "a\na\na"
val fontSize = 100.sp
val fontSizeInPx = fontSize.toPx()
val maxLines = text.lines().size
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
maxLines = maxLines
)
val expectHeight = (maxLines + (maxLines - 1) * 0.2f) * fontSizeInPx
assertThat(paragraph.height).isEqualTo(expectHeight)
}
}
@Test
fun maxLines_withMaxLineGreaterThanTextLines() {
with(defaultDensity) {
val text = "a\na\na"
val fontSize = 100.sp
val fontSizeInPx = fontSize.toPx()
val lineCount = text.lines().size
val maxLines = lineCount + 1
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
maxLines = maxLines,
constraints = ParagraphConstraints(width = 200f)
)
val expectHeight = (lineCount + (lineCount - 1) * 0.2f) * fontSizeInPx
assertThat(paragraph.height).isEqualTo(expectHeight)
}
}
@Test
fun maxLines_paintDifferently() {
with(defaultDensity) {
val text = "a\na\na"
val fontSize = 100.sp
val fontSizeInPx = fontSize.toPx()
val maxLines = 1
val paragraphWithMaxLine = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
maxLines = maxLines,
constraints = ParagraphConstraints(width = fontSizeInPx)
)
val paragraphNoMaxLine = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = fontSizeInPx)
)
// Make sure the maxLine is applied correctly
assertThat(paragraphNoMaxLine.height).isGreaterThan(paragraphWithMaxLine.height)
val imageNoMaxLine = ImageAsset(
paragraphNoMaxLine.width.roundToInt(),
paragraphNoMaxLine.height.roundToInt(),
ImageAssetConfig.Argb8888
)
// Same size with imageNoMaxLine for comparison
val imageWithMaxLine = ImageAsset(
paragraphNoMaxLine.width.roundToInt(),
paragraphNoMaxLine.height.roundToInt(),
ImageAssetConfig.Argb8888
)
paragraphNoMaxLine.paint(Canvas(imageNoMaxLine))
paragraphWithMaxLine.paint(Canvas(imageWithMaxLine))
assertThat(imageNoMaxLine.asAndroidBitmap()).isNotEqualToBitmap(imageWithMaxLine
.asAndroidBitmap())
}
}
@Test
fun didExceedMaxLines_withMaxLinesSmallerThanTextLines_returnsTrue() {
val text = "aaa\naa"
val maxLines = text.lines().size - 1
val paragraph = simpleParagraph(
text = text,
maxLines = maxLines
)
assertThat(paragraph.didExceedMaxLines).isTrue()
}
@Test
fun didExceedMaxLines_withMaxLinesEqualToTextLines_returnsFalse() {
val text = "aaa\naa"
val maxLines = text.lines().size
val paragraph = simpleParagraph(
text = text,
maxLines = maxLines
)
assertThat(paragraph.didExceedMaxLines).isFalse()
}
@Test
fun didExceedMaxLines_withMaxLinesGreaterThanTextLines_returnsFalse() {
val text = "aaa\naa"
val maxLines = text.lines().size + 1
val paragraph = simpleParagraph(
text = text,
maxLines = maxLines
)
assertThat(paragraph.didExceedMaxLines).isFalse()
}
@Test
fun didExceedMaxLines_withMaxLinesSmallerThanTextLines_withLineWrap_returnsTrue() {
with(defaultDensity) {
val text = "aa"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val maxLines = 1
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
maxLines = maxLines,
// One line can only contain 1 character
constraints = ParagraphConstraints(width = fontSizeInPx)
)
assertThat(paragraph.didExceedMaxLines).isTrue()
}
}
@Test
fun didExceedMaxLines_withMaxLinesEqualToTextLines_withLineWrap_returnsFalse() {
val text = "a"
val maxLines = text.lines().size
val paragraph = simpleParagraph(
text = text,
maxLines = maxLines
)
assertThat(paragraph.didExceedMaxLines).isFalse()
}
@Test
fun didExceedMaxLines_withMaxLinesGreaterThanTextLines_withLineWrap_returnsFalse() {
with(defaultDensity) {
val text = "aa"
val maxLines = 3
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
maxLines = maxLines,
// One line can only contain 1 character
constraints = ParagraphConstraints(width = fontSizeInPx)
)
assertThat(paragraph.didExceedMaxLines).isFalse()
}
}
@Test
fun didExceedMaxLines_ellipsis_withMaxLinesSmallerThanTextLines_returnsTrue() {
val text = "aaa\naa"
val maxLines = text.lines().size - 1
val paragraph = simpleParagraph(
text = text,
maxLines = maxLines,
ellipsis = true
)
assertThat(paragraph.didExceedMaxLines).isTrue()
}
@Test
fun didExceedMaxLines_ellipsis_withMaxLinesEqualToTextLines_returnsFalse() {
val text = "aaa\naa"
val maxLines = text.lines().size
val paragraph = simpleParagraph(
text = text,
maxLines = maxLines,
ellipsis = true
)
assertThat(paragraph.didExceedMaxLines).isFalse()
}
@Test
fun didExceedMaxLines_ellipsis_withMaxLinesGreaterThanTextLines_returnsFalse() {
val text = "aaa\naa"
val maxLines = text.lines().size + 1
val paragraph = simpleParagraph(
text = text,
maxLines = maxLines,
ellipsis = true
)
assertThat(paragraph.didExceedMaxLines).isFalse()
}
@Test
fun didExceedMaxLines_ellipsis_withMaxLinesSmallerThanTextLines_withLineWrap_returnsTrue() {
with(defaultDensity) {
val text = "aa"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val maxLines = 1
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
maxLines = maxLines,
ellipsis = true,
// One line can only contain 1 character
constraints = ParagraphConstraints(width = fontSizeInPx)
)
assertThat(paragraph.didExceedMaxLines).isTrue()
}
}
@Test
fun didExceedMaxLines_ellipsis_withMaxLinesEqualToTextLines_withLineWrap_returnsFalse() {
val text = "a"
val maxLines = text.lines().size
val paragraph = simpleParagraph(
text = text,
maxLines = maxLines,
ellipsis = true
)
assertThat(paragraph.didExceedMaxLines).isFalse()
}
@Test
fun didExceedMaxLines_ellipsis_withMaxLinesGreaterThanTextLines_withLineWrap_returnsFalse() {
with(defaultDensity) {
val text = "aa"
val maxLines = 3
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
maxLines = maxLines,
ellipsis = true,
// One line can only contain 1 character
constraints = ParagraphConstraints(width = fontSizeInPx)
)
assertThat(paragraph.didExceedMaxLines).isFalse()
}
}
@Test
fun textAlign_defaultValue_alignsStart() {
with(defaultDensity) {
val textLTR = "aa"
val textRTL = "\u05D0\u05D0"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val layoutLTRWidth = (textLTR.length + 2) * fontSizeInPx
val paragraphLTR = simpleParagraph(
text = textLTR,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = layoutLTRWidth)
)
val layoutRTLWidth = (textRTL.length + 2) * fontSizeInPx
val paragraphRTL = simpleParagraph(
text = textRTL,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = layoutRTLWidth)
)
// When textAlign is TextAlign.start, LTR aligns to left, RTL aligns to right.
assertThat(paragraphLTR.getLineLeft(0)).isZero()
assertThat(paragraphRTL.getLineRight(0)).isEqualTo(layoutRTLWidth)
}
}
@Test
fun textAlign_whenAlignLeft_returnsZeroForGetLineLeft() {
with(defaultDensity) {
val texts = listOf("aa", "\u05D0\u05D0")
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
texts.map { text ->
val layoutWidth = (text.length + 2) * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textAlign = TextAlign.Left
),
constraints = ParagraphConstraints(width = layoutWidth)
)
assertThat(paragraph.getLineLeft(0)).isZero()
}
}
}
@Test
fun textAlign_whenAlignRight_returnsLayoutWidthForGetLineRight() {
with(defaultDensity) {
val texts = listOf("aa", "\u05D0\u05D0")
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
texts.map { text ->
val layoutWidth = (text.length + 2) * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textAlign = TextAlign.Right
),
constraints = ParagraphConstraints(width = layoutWidth)
)
assertThat(paragraph.getLineRight(0)).isEqualTo(layoutWidth)
}
}
}
@Test
fun textAlign_whenAlignCenter_textIsCentered() {
with(defaultDensity) {
val texts = listOf("aa", "\u05D0\u05D0")
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
texts.map { text ->
val layoutWidth = (text.length + 2) * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textAlign = TextAlign.Center
),
constraints = ParagraphConstraints(width = layoutWidth)
)
val textWidth = text.length * fontSizeInPx
assertThat(paragraph.getLineLeft(0)).isEqualTo(layoutWidth / 2 - textWidth / 2)
assertThat(paragraph.getLineRight(0)).isEqualTo(layoutWidth / 2 + textWidth / 2)
}
}
}
@Test
fun textAlign_whenAlignStart_withLTR_returnsZeroForGetLineLeft() {
with(defaultDensity) {
val text = "aa"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val layoutWidth = (text.length + 2) * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textAlign = TextAlign.Start
),
constraints = ParagraphConstraints(width = layoutWidth)
)
assertThat(paragraph.getLineLeft(0)).isZero()
}
}
@Test
fun textAlign_whenAlignEnd_withLTR_returnsLayoutWidthForGetLineRight() {
with(defaultDensity) {
val text = "aa"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val layoutWidth = (text.length + 2) * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textAlign = TextAlign.End
),
constraints = ParagraphConstraints(width = layoutWidth)
)
assertThat(paragraph.getLineRight(0)).isEqualTo(layoutWidth)
}
}
@Test
fun textAlign_whenAlignStart_withRTL_returnsLayoutWidthForGetLineRight() {
with(defaultDensity) {
val text = "\u05D0\u05D0"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val layoutWidth = (text.length + 2) * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textAlign = TextAlign.Start
),
constraints = ParagraphConstraints(width = layoutWidth)
)
assertThat(paragraph.getLineRight(0)).isEqualTo(layoutWidth)
}
}
@Test
fun textAlign_whenAlignEnd_withRTL_returnsZeroForGetLineLeft() {
with(defaultDensity) {
val text = "\u05D0\u05D0"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val layoutWidth = (text.length + 2) * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textAlign = TextAlign.End
),
constraints = ParagraphConstraints(width = layoutWidth)
)
assertThat(paragraph.getLineLeft(0)).isZero()
}
}
@Test
@SdkSuppress(minSdkVersion = 28)
// We have to test justification above API 28 because of this bug b/68009059, where devices
// before API 28 may have an extra space at the end of line.
fun textAlign_whenAlignJustify_justifies() {
with(defaultDensity) {
val text = "a a a"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val layoutWidth = ("a a".length + 1) * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textAlign = TextAlign.Justify
),
constraints = ParagraphConstraints(width = layoutWidth)
)
assertThat(paragraph.getLineLeft(0)).isZero()
assertThat(paragraph.getLineRight(0)).isEqualTo(layoutWidth)
// Last line should align start
assertThat(paragraph.getLineLeft(1)).isZero()
}
}
@Test
fun textDirection_whenLTR_dotIsOnRight() {
with(defaultDensity) {
val text = "a.."
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val layoutWidth = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Ltr
),
constraints = ParagraphConstraints(width = layoutWidth)
)
// The position of the last character in display order.
val position = Offset(("a.".length * fontSizeInPx + 1), (fontSizeInPx / 2))
val charIndex = paragraph.getOffsetForPosition(position)
assertThat(charIndex).isEqualTo(2)
}
}
@Test
fun textDirection_whenRTL_dotIsOnLeft() {
with(defaultDensity) {
val text = "a.."
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val layoutWidth = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textDirection = TextDirection.Rtl
),
constraints = ParagraphConstraints(width = layoutWidth)
)
// The position of the first character in display order.
val position = Offset((fontSizeInPx / 2 + 1), (fontSizeInPx / 2))
val charIndex = paragraph.getOffsetForPosition(position)
assertThat(charIndex).isEqualTo(2)
}
}
@Test
fun textDirection_whenDefault_withoutStrongChar_directionIsLTR() {
with(defaultDensity) {
val text = "..."
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val layoutWidth = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize, localeList = ltrLocaleList),
constraints = ParagraphConstraints(width = layoutWidth)
)
for (i in 0..text.length) {
// The position of the i-th character in display order.
val position = Offset((i * fontSizeInPx + 1), (fontSizeInPx / 2))
val charIndex = paragraph.getOffsetForPosition(position)
assertThat(charIndex).isEqualTo(i)
}
}
}
@Test
fun textDirection_whenDefault_withFirstStrongCharLTR_directionIsLTR() {
with(defaultDensity) {
val text = "a\u05D0."
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val layoutWidth = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = layoutWidth)
)
for (i in text.indices) {
// The position of the i-th character in display order.
val position = Offset((i * fontSizeInPx + 1), (fontSizeInPx / 2))
val charIndex = paragraph.getOffsetForPosition(position)
assertThat(charIndex).isEqualTo(i)
}
}
}
@Test
fun textDirection_whenDefault_withFirstStrongCharRTL_directionIsRTL() {
with(defaultDensity) {
val text = "\u05D0a."
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val layoutWidth = text.length * fontSizeInPx
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = layoutWidth)
)
// The first character in display order should be '.'
val position = Offset((fontSizeInPx / 2 + 1), (fontSizeInPx / 2))
val index = paragraph.getOffsetForPosition(position)
assertThat(index).isEqualTo(2)
}
}
@Test
fun getLineTop() {
with(defaultDensity) {
val text = "aaa\nbbb"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize)
)
assertThat(paragraph.getLineTop(0)).isZero()
assertThat(paragraph.getLineTop(1)).isEqualTo(fontSizeInPx)
}
}
@Test
fun getLineBottom() {
with(defaultDensity) {
val text = "aaa\nbbb"
val fontSize = 50.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize)
)
assertThat(paragraph.getLineBottom(0)).isEqualTo(fontSizeInPx)
assertThat(paragraph.getLineBottom(1)).isEqualTo(fontSize.value * (2f + 1f / 5f))
}
}
@Test
fun getLineForOffset_withNewline() {
val text = "aaa\nbbb"
val paragraph = simpleParagraph(
text = text,
constraints = ParagraphConstraints(Float.MAX_VALUE)
)
for (i in 0..2) {
assertThat(paragraph.getLineForOffset(i)).isEqualTo(0)
}
for (i in 4..6) {
assertThat(paragraph.getLineForOffset(i)).isEqualTo(1)
}
}
@Test
fun getLineForOffset_newline_belongsToPreviousLine() {
val text = "aaa\nbbb\n"
val paragraph = simpleParagraph(
text = text,
constraints = ParagraphConstraints(Float.MAX_VALUE)
)
assertThat(paragraph.getLineForOffset(3)).isEqualTo(0)
assertThat(paragraph.getLineForOffset(7)).isEqualTo(1)
}
@Test
fun getLineForOffset_outOfBoundary() {
val text = "aaa\nbbb"
val paragraph = simpleParagraph(
text = text,
constraints = ParagraphConstraints(Float.MAX_VALUE)
)
assertThat(paragraph.getLineForOffset(-1)).isEqualTo(0)
assertThat(paragraph.getLineForOffset(-2)).isEqualTo(0)
assertThat(paragraph.getLineForOffset(text.length)).isEqualTo(1)
assertThat(paragraph.getLineForOffset(text.length + 1)).isEqualTo(1)
}
@Test
fun getLineForOffset_ellipsisApplied() {
val text = "aaa\nbbb"
val paragraph = simpleParagraph(
text = text,
maxLines = 1,
ellipsis = true,
style = TextStyle(),
constraints = ParagraphConstraints(Float.MAX_VALUE)
)
for (i in 0..2) {
assertThat(paragraph.getLineForOffset(i)).isEqualTo(0)
}
assertThat(paragraph.getLineForOffset(3)).isEqualTo(0)
for (i in 4..6) {
// It returns 0 because the second line(index 1) is ellipsized
assertThat(paragraph.getLineForOffset(i)).isEqualTo(0)
}
// It returns 0 since the paragraph actually has 1 line
assertThat(paragraph.getLineForOffset(text.length + 1)).isEqualTo(0)
}
@Test
fun getLineStart_linebreak() {
with(defaultDensity) {
val text = "aaabbb"
val fontSize = 50f
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize.sp
),
constraints = ParagraphConstraints(fontSize * 3)
)
// Prerequisite check for the this test.
assertThat(paragraph.lineCount).isEqualTo(2)
assertThat(paragraph.getLineStart(0)).isEqualTo(0)
assertThat(paragraph.getLineStart(1)).isEqualTo(3)
}
}
@Test
fun getLineStart_newline() {
val text = "aaa\nbbb"
val paragraph = simpleParagraph(
text = text,
constraints = ParagraphConstraints(Float.MAX_VALUE)
)
// Prerequisite check for the this test.
assertThat(paragraph.lineCount).isEqualTo(text.lines().size)
assertThat(paragraph.getLineStart(0)).isEqualTo(0)
// First char after '\n'
assertThat(paragraph.getLineStart(1))
.isEqualTo(text.indexOfFirst { ch -> ch == '\n' } + 1)
}
@Test
fun getLineStart_emptyLine() {
val text = "aaa\n"
val paragraph = simpleParagraph(
text = text,
constraints = ParagraphConstraints(Float.MAX_VALUE)
)
// Prerequisite check for the this test.
assertThat(paragraph.lineCount).isEqualTo(2)
assertThat(paragraph.getLineStart(0)).isEqualTo(0)
assertThat(paragraph.getLineStart(1)).isEqualTo(4)
}
@Test
fun getLineEnd_linebreak() {
val text = "aaabbb"
val fontSize = 50f
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize.sp
),
constraints = ParagraphConstraints(fontSize * 3),
density = defaultDensity
)
// Prerequisite check for the this test.
assertThat(paragraph.lineCount).isEqualTo(2)
assertThat(paragraph.getLineStart(0)).isEqualTo(0)
assertThat(paragraph.getLineStart(1)).isEqualTo(3)
}
@Test
fun getLineEnd_newline() {
val text = "aaa\nbbb"
val paragraph = simpleParagraph(
text = text,
constraints = ParagraphConstraints(Float.MAX_VALUE)
)
// Prerequisite check for the this test.
assertThat(paragraph.lineCount).isEqualTo(text.lines().size)
assertThat(paragraph.getLineEnd(0)).isEqualTo(text.indexOfFirst { ch -> ch == '\n' } + 1)
assertThat(paragraph.getLineEnd(1)).isEqualTo(text.length)
}
@Test
fun getLineEnd_emptyLine() {
val text = "aaa\n"
val paragraph = simpleParagraph(
text = text,
constraints = ParagraphConstraints(Float.MAX_VALUE)
)
// Prerequisite check for the this test.
assertThat(paragraph.lineCount).isEqualTo(2)
assertThat(paragraph.getLineEnd(0)).isEqualTo(4)
assertThat(paragraph.getLineEnd(1)).isEqualTo(4)
}
@Test
fun getLineEllipsisOffset() {
val text = "aaa\nbbb\nccc"
val paragraph = simpleParagraph(
text = text,
maxLines = 2,
ellipsis = true,
constraints = ParagraphConstraints(Float.MAX_VALUE)
)
assertThat(paragraph.lineCount).isEqualTo(2)
assertThat(paragraph.getLineEllipsisOffset(0)).isEqualTo(0)
assertThat(paragraph.getLineEllipsisOffset(1)).isGreaterThan(0)
}
@Test
fun getLineEllipsisCount() {
val text = "aaa\nbbb\nccc"
val paragraph = simpleParagraph(
text = text,
maxLines = 2,
ellipsis = true,
constraints = ParagraphConstraints(Float.MAX_VALUE)
)
assertThat(paragraph.lineCount).isEqualTo(2)
assertThat(paragraph.getLineEllipsisCount(0)).isEqualTo(0)
assertThat(paragraph.getLineEllipsisCount(1)).isGreaterThan(0)
// Ellipsis offset plus ellipsis count equals to length of the line.
assertThat(paragraph.getLineEllipsisCount(1) + paragraph.getLineEllipsisOffset(1))
.isEqualTo(paragraph.getLineEnd(1) - paragraph.getLineStart(1))
}
@Test
fun lineHeight_inSp() {
with(defaultDensity) {
val text = "abcdefgh"
val fontSize = 20f
// Make the layout 4 lines
val layoutWidth = text.length * fontSize / 4
val lineHeight = 30f
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize.sp,
lineHeight = lineHeight.sp
),
constraints = ParagraphConstraints(width = layoutWidth)
)
assertThat(paragraph.lineCount).isEqualTo(4)
// First/last line is influenced by top/bottom padding
for (i in 1 until paragraph.lineCount - 1) {
val actualHeight = paragraph.getLineHeight(i)
// In the sample_font.ttf, the height of the line should be
// fontSize + 0.2f * fontSize(line gap)
assertWithMessage("line number $i").that(actualHeight).isEqualTo(lineHeight)
}
}
}
@Test
fun lineHeight_InEm() {
with(defaultDensity) {
val text = "abcdefgh"
val fontSize = 20f
// Make the layout 4 lines
val layoutWidth = text.length * fontSize / 4
val lineHeight = 1.5f
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize.sp, lineHeight = lineHeight.em),
constraints = ParagraphConstraints(width = layoutWidth)
)
assertThat(paragraph.lineCount).isEqualTo(4)
// First/last line is influenced by top/bottom padding
for (i in 1 until paragraph.lineCount - 1) {
val actualHeight = paragraph.getLineHeight(i)
// In the sample_font.ttf, the height of the line should be
// fontSize + 0.2f * fontSize(line gap)
assertWithMessage("line number $i")
.that(actualHeight).isEqualTo(lineHeight * fontSize)
}
}
}
@Test
fun testAnnotatedString_setFontSizeOnWholeText() {
with(defaultDensity) {
val text = "abcde"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val spanStyle = SpanStyle(fontSize = fontSize)
val paragraphWidth = fontSizeInPx * text.length
val paragraph = simpleParagraph(
text = text,
spanStyles = listOf(AnnotatedString.Range(spanStyle, 0, text.length)),
constraints = ParagraphConstraints(width = paragraphWidth)
)
// Make sure there is only one line, so that we can use getLineRight to test fontSize.
assertThat(paragraph.lineCount).isEqualTo(1)
// Notice that in this test font, the width of character equals to fontSize.
assertThat(paragraph.getLineWidth(0)).isEqualTo(fontSizeInPx * text.length)
}
}
@Test
fun testAnnotatedString_setFontSizeOnPartOfText() {
with(defaultDensity) {
val text = "abcde"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val spanStyleFontSize = 30.sp
val spanStyleFontSizeInPx = spanStyleFontSize.toPx()
val spanStyle = SpanStyle(fontSize = spanStyleFontSize)
val paragraphWidth = spanStyleFontSizeInPx * text.length
val paragraph = simpleParagraph(
text = text,
spanStyles = listOf(AnnotatedString.Range(spanStyle, 0, "abc".length)),
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(width = paragraphWidth)
)
// Make sure there is only one line, so that we can use getLineRight to test fontSize.
assertThat(paragraph.lineCount).isEqualTo(1)
// Notice that in this test font, the width of character equals to fontSize.
val expectedLineRight = "abc".length * spanStyleFontSizeInPx +
"de".length * fontSizeInPx
assertThat(paragraph.getLineWidth(0)).isEqualTo(expectedLineRight)
}
}
@Test
fun testAnnotatedString_seFontSizeTwice_lastOneOverwrite() {
with(defaultDensity) {
val text = "abcde"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val spanStyle = SpanStyle(fontSize = fontSize)
val fontSizeOverwrite = 30.sp
val fontSizeOverwriteInPx = fontSizeOverwrite.toPx()
val spanStyleOverwrite = SpanStyle(fontSize = fontSizeOverwrite)
val paragraphWidth = fontSizeOverwriteInPx * text.length
val paragraph = simpleParagraph(
text = text,
spanStyles = listOf(
AnnotatedString.Range(spanStyle, 0, text.length),
AnnotatedString.Range(spanStyleOverwrite, 0, "abc".length)
),
constraints = ParagraphConstraints(width = paragraphWidth)
)
// Make sure there is only one line, so that we can use getLineRight to test fontSize.
assertThat(paragraph.lineCount).isEqualTo(1)
// Notice that in this test font, the width of character equals to fontSize.
val expectedWidth = "abc".length * fontSizeOverwriteInPx + "de".length * fontSizeInPx
assertThat(paragraph.getLineWidth(0)).isEqualTo(expectedWidth)
}
}
@Test
fun testAnnotatedString_fontSizeScale() {
with(defaultDensity) {
val text = "abcde"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val em = 0.5.em
val spanStyle = SpanStyle(fontSize = em)
val paragraph = simpleParagraph(
text = text,
spanStyles = listOf(AnnotatedString.Range(spanStyle, 0, text.length)),
style = TextStyle(fontSize = fontSize)
)
assertThat(paragraph.getLineRight(0))
.isEqualTo(text.length * fontSizeInPx * em.value)
}
}
@Test
fun testAnnotatedString_fontSizeScaleNested() {
with(defaultDensity) {
val text = "abcde"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val em = 0.5f.em
val spanStyle = SpanStyle(fontSize = em)
val emNested = 2f.em
val spanStyleNested = SpanStyle(fontSize = emNested)
val paragraph = simpleParagraph(
text = text,
spanStyles = listOf(
AnnotatedString.Range(spanStyle, 0, text.length),
AnnotatedString.Range(spanStyleNested, 0, text.length)
),
style = TextStyle(fontSize = fontSize)
)
assertThat(paragraph.getLineRight(0))
.isEqualTo(text.length * fontSizeInPx * em.value * emNested.value)
}
}
@Test
fun testAnnotatedString_fontSizeScaleWithFontSizeFirst() {
with(defaultDensity) {
val text = "abcde"
val paragraphFontSize = 20.sp
val fontSize = 30.sp
val fontSizeInPx = fontSize.toPx()
val fontSizeStyle = SpanStyle(fontSize = fontSize)
val em = 0.5f.em
val fontSizeScaleStyle = SpanStyle(fontSize = em)
val paragraph = simpleParagraph(
text = text,
spanStyles = listOf(
AnnotatedString.Range(fontSizeStyle, 0, text.length),
AnnotatedString.Range(fontSizeScaleStyle, 0, text.length)
),
style = TextStyle(fontSize = paragraphFontSize)
)
assertThat(paragraph.getLineRight(0))
.isEqualTo(text.length * fontSizeInPx * em.value)
}
}
@Test
fun testAnnotatedString_fontSizeScaleWithFontSizeSecond() {
with(defaultDensity) {
val text = "abcde"
val paragraphFontSize = 20.sp
val fontSize = 30.sp
val fontSizeInPx = fontSize.toPx()
val fontSizeStyle = SpanStyle(fontSize = fontSize)
val em = 0.5f.em
val fontSizeScaleStyle = SpanStyle(fontSize = em)
val paragraph = simpleParagraph(
text = text,
spanStyles = listOf(
AnnotatedString.Range(fontSizeScaleStyle, 0, text.length),
AnnotatedString.Range(fontSizeStyle, 0, text.length)
),
style = TextStyle(fontSize = paragraphFontSize)
)
assertThat(paragraph.getLineRight(0)).isEqualTo(text.length * fontSizeInPx)
}
}
@Test
fun testAnnotatedString_fontSizeScaleWithFontSizeNested() {
with(defaultDensity) {
val text = "abcde"
val paragraphFontSize = 20.sp
val fontSize = 30.sp
val fontSizeInPx = fontSize.toPx()
val fontSizeStyle = SpanStyle(fontSize = fontSize)
val em1 = 0.5f.em
val fontSizeScaleStyle1 = SpanStyle(fontSize = em1)
val em2 = 2f.em
val fontSizeScaleStyle2 = SpanStyle(fontSize = em2)
val paragraph = simpleParagraph(
text = text,
spanStyles = listOf(
AnnotatedString.Range(fontSizeScaleStyle1, 0, text.length),
AnnotatedString.Range(fontSizeStyle, 0, text.length),
AnnotatedString.Range(fontSizeScaleStyle2, 0, text.length)
),
style = TextStyle(fontSize = paragraphFontSize)
)
assertThat(paragraph.getLineRight(0))
.isEqualTo(text.length * fontSizeInPx * em2.value)
}
}
@Test
fun testAnnotatedString_setLetterSpacing_inEm_OnWholeText() {
with(defaultDensity) {
val text = "abcde"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val letterSpacing = 5.0f
val spanStyle = SpanStyle(letterSpacing = letterSpacing.em)
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
spanStyles = listOf(AnnotatedString.Range(spanStyle, 0, text.length)),
constraints = ParagraphConstraints(width = Float.MAX_VALUE)
)
assertThat(paragraph.lineCount).isEqualTo(1)
// Notice that in this test font, the width of character equals to fontSize.
assertThat(paragraph.getLineWidth(0))
.isEqualTo(fontSizeInPx * text.length * (1 + letterSpacing))
}
}
@Test
fun testAnnotatedString_setLetterSpacing_inSp_OnWholeText() {
with(defaultDensity) {
val text = "abcde"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val letterSpacing = 5.0f
val spanStyle = SpanStyle(letterSpacing = letterSpacing.sp)
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
spanStyles = listOf(AnnotatedString.Range(spanStyle, 0, text.length)),
constraints = ParagraphConstraints(width = Float.MAX_VALUE)
)
assertThat(paragraph.lineCount).isEqualTo(1)
// Notice that in this test font, the width of character equals to fontSize.
assertThat(paragraph.getLineWidth(0))
.isEqualTo((fontSizeInPx + letterSpacing) * text.length)
}
}
@Test
fun testAnnotatedString_setLetterSpacingOnPartText() {
with(defaultDensity) {
val text = "abcde"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val letterSpacing = 5.0f
val spanStyle = SpanStyle(letterSpacing = letterSpacing.em)
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
spanStyles = listOf(AnnotatedString.Range(spanStyle, 0, "abc".length)),
constraints = ParagraphConstraints(width = Float.MAX_VALUE)
)
assertThat(paragraph.lineCount).isEqualTo(1)
// Notice that in this test font, the width of character equals to fontSize.
val expectedWidth = ("abc".length * letterSpacing + text.length) * fontSizeInPx
assertThat(paragraph.getLineWidth(0)).isEqualTo(expectedWidth)
}
}
@Test
fun testAnnotatedString_setLetterSpacingTwice_lastOneOverwrite() {
with(defaultDensity) {
val text = "abcde"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val letterSpacing = 5.0f
val spanStyle = SpanStyle(letterSpacing = letterSpacing.em)
val letterSpacingOverwrite = 10.0f
val spanStyleOverwrite = SpanStyle(letterSpacing = letterSpacingOverwrite.em)
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
spanStyles = listOf(
AnnotatedString.Range(spanStyle, 0, text.length),
AnnotatedString.Range(spanStyleOverwrite, 0, "abc".length)
),
constraints = ParagraphConstraints(width = Float.MAX_VALUE)
)
assertThat(paragraph.lineCount).isEqualTo(1)
// Notice that in this test font, the width of character equals to fontSize.
val expectedWidth = "abc".length * (1 + letterSpacingOverwrite) * fontSizeInPx +
"de".length * (1 + letterSpacing) * fontSizeInPx
assertThat(paragraph.getLineWidth(0)).isEqualTo(expectedWidth)
}
}
@Test
fun testAnnotatedString_setLetterSpacing_inEm_withFontSize() {
with(defaultDensity) {
val text = "abcde"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val letterSpacing = 2f
val letterSpacingStyle = SpanStyle(letterSpacing = letterSpacing.em)
val fontSizeOverwrite = 30.sp
val fontSizeOverwriteInPx = fontSizeOverwrite.toPx()
val fontSizeStyle = SpanStyle(fontSize = fontSizeOverwrite)
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
spanStyles = listOf(
AnnotatedString.Range(letterSpacingStyle, 0, text.length),
AnnotatedString.Range(fontSizeStyle, 0, "abc".length)
),
constraints = ParagraphConstraints(width = Float.MAX_VALUE)
)
assertThat(paragraph.lineCount).isEqualTo(1)
// Notice that in this test font, the width of character equals to fontSize.
val expectedWidth = (1 + letterSpacing) *
("abc".length * fontSizeOverwriteInPx + "de".length * fontSizeInPx)
assertThat(paragraph.getLineWidth(0)).isEqualTo(expectedWidth)
}
}
@Test
fun testAnnotatedString_setLetterSpacing_inEm_withScaleX() {
with(defaultDensity) {
val text = "abcde"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val letterSpacing = 2f
val letterSpacingStyle = SpanStyle(letterSpacing = letterSpacing.em)
val scaleX = 1.5f
val scaleXStyle = SpanStyle(textGeometricTransform = TextGeometricTransform(scaleX))
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
spanStyles = listOf(
AnnotatedString.Range(letterSpacingStyle, 0, text.length),
AnnotatedString.Range(scaleXStyle, 0, "abc".length)
),
constraints = ParagraphConstraints(width = Float.MAX_VALUE)
)
assertThat(paragraph.lineCount).isEqualTo(1)
// Notice that in this test font, the width of character equals to fontSize.
val expectedWidth = (1 + letterSpacing) *
("abc".length * fontSizeInPx * scaleX + "de".length * fontSizeInPx)
assertThat(paragraph.getLineWidth(0)).isEqualTo(expectedWidth)
}
}
@Test
fun testAnnotatedString_setLetterSpacing_inSp_withFontSize() {
with(defaultDensity) {
val text = "abcde"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val letterSpacing = 10.sp
val letterSpacingInPx = letterSpacing.toPx()
val letterSpacingStyle = SpanStyle(letterSpacing = letterSpacing)
val fontSizeOverwrite = 30.sp
val fontSizeOverwriteInPx = fontSizeOverwrite.toPx()
val fontSizeStyle = SpanStyle(fontSize = fontSizeOverwrite)
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
spanStyles = listOf(
AnnotatedString.Range(letterSpacingStyle, 0, text.length),
AnnotatedString.Range(fontSizeStyle, 0, "abc".length)
),
constraints = ParagraphConstraints(width = Float.MAX_VALUE)
)
assertThat(paragraph.lineCount).isEqualTo(1)
// Notice that in this test font, the width of character equals to fontSize.
val expectedWidth = text.length * letterSpacingInPx +
("abc".length * fontSizeOverwriteInPx + "de".length * fontSizeInPx)
assertThat(paragraph.getLineWidth(0)).isEqualTo(expectedWidth)
}
}
@Test
fun testAnnotatedString_setLetterSpacing_inSp_withScaleX() {
with(defaultDensity) {
val text = "abcde"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val letterSpacing = 10.sp
val letterSpacingInPx = letterSpacing.toPx()
val letterSpacingStyle = SpanStyle(letterSpacing = letterSpacing)
val scaleX = 1.5f
val scaleXStyle = SpanStyle(textGeometricTransform = TextGeometricTransform(scaleX))
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
spanStyles = listOf(
AnnotatedString.Range(letterSpacingStyle, 0, text.length),
AnnotatedString.Range(scaleXStyle, 0, "abc".length)
),
constraints = ParagraphConstraints(width = Float.MAX_VALUE)
)
assertThat(paragraph.lineCount).isEqualTo(1)
// Notice that in this test font, the width of character equals to fontSize.
val expectedWidth = text.length * letterSpacingInPx +
("abc".length * fontSizeInPx * scaleX + "de".length * fontSizeInPx)
assertThat(paragraph.getLineWidth(0)).isEqualTo(expectedWidth)
}
}
@Test
fun testAnnotatedString_setLetterSpacing_inSp_after_inEm() {
with(defaultDensity) {
val text = "abcde"
val fontSize = 20f
val letterSpacingEm = 1f
val letterSpacingEmStyle = SpanStyle(letterSpacing = letterSpacingEm.em)
val letterSpacingSp = 10f
val letterSpacingSpStyle = SpanStyle(letterSpacing = letterSpacingSp.sp)
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize.sp),
spanStyles = listOf(
AnnotatedString.Range(letterSpacingEmStyle, 0, text.length),
AnnotatedString.Range(letterSpacingSpStyle, 0, "abc".length)
),
constraints = ParagraphConstraints(width = Float.MAX_VALUE)
)
assertThat(paragraph.lineCount).isEqualTo(1)
// Notice that in this test font, the width of character equals to fontSize.
val expectedWidth = fontSize * text.length + "abc".length * letterSpacingSp +
"de".length * fontSize * letterSpacingEm
assertThat(paragraph.getLineWidth(0)).isEqualTo(expectedWidth)
}
}
@Test
fun testAnnotatedString_setLetterSpacing_inEm_after_inSp() {
with(defaultDensity) {
val text = "abcde"
val fontSize = 20f
val letterSpacingEm = 1f
val letterSpacingEmStyle = SpanStyle(letterSpacing = letterSpacingEm.em)
val letterSpacingSp = 10f
val letterSpacingSpStyle = SpanStyle(letterSpacing = letterSpacingSp.sp)
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize.sp),
spanStyles = listOf(
AnnotatedString.Range(letterSpacingSpStyle, 0, "abc".length),
AnnotatedString.Range(letterSpacingEmStyle, 0, text.length)
),
constraints = ParagraphConstraints(width = 500f)
)
assertThat(paragraph.lineCount).isEqualTo(1)
// Notice that in this test font, the width of character equals to fontSize.
val expectedWidth = fontSize * text.length * (1 + letterSpacingEm)
assertThat(paragraph.getLineWidth(0)).isEqualTo(expectedWidth)
}
}
@Test
fun textIndent_inSp_onSingleLine() {
with(defaultDensity) {
val text = "abc"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val indent = 20.sp
val indentInPx = indent.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize,
textIndent = TextIndent(firstLine = indent),
fontFamily = fontFamilyMeasureFont
)
)
// This position should point to the first character 'a' if indent is applied.
// Otherwise this position will point to the second character 'b'.
val position = Offset((indentInPx + 1), (fontSizeInPx / 2))
// The offset corresponding to the position should be the first char 'a'.
assertThat(paragraph.getOffsetForPosition(position)).isZero()
}
}
@Test
fun textIndent_inSp_onFirstLine() {
with(defaultDensity) {
val text = "abcdef"
val fontSize = 20f
val indent = 15f
val paragraphWidth = "abcd".length * fontSize
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontSize = fontSize.sp,
textIndent = TextIndent(firstLine = indent.sp),
fontFamily = fontFamilyMeasureFont
),
constraints = ParagraphConstraints(width = paragraphWidth)
)
assertThat(paragraph.lineCount).isEqualTo(2)
assertThat(paragraph.getHorizontalPosition(0, true)).isEqualTo(indent)
}
}
@Test
fun textIndent_inSp_onRestLine() {
with(defaultDensity) {
val text = "abcde"
val fontSize = 20f
val indent = 20f
val paragraphWidth = "abc".length * fontSize
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
textIndent = TextIndent(restLine = indent.sp),
fontSize = fontSize.sp,
fontFamily = fontFamilyMeasureFont
),
constraints = ParagraphConstraints(width = paragraphWidth)
)
// check the position of the first character in second line: "d" should be indented
assertThat(paragraph.getHorizontalPosition(3, true)).isEqualTo(indent)
}
}
@Test
fun textIndent_inEm_onSingleLine() {
with(defaultDensity) {
val text = "abc"
val fontSize = 20f
val indent = 1.5f
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
textIndent = TextIndent(firstLine = indent.em),
fontSize = fontSize.sp,
fontFamily = fontFamilyMeasureFont
)
)
assertThat(paragraph.getHorizontalPosition(0, true)).isEqualTo(indent * fontSize)
}
}
@Test
fun textIndent_inEm_onFirstLine() {
with(defaultDensity) {
val text = "abcdef"
val fontSize = 20f
val indent = 1.5f
val paragraphWidth = "abcd".length * fontSize
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
textIndent = TextIndent(firstLine = indent.em),
fontSize = fontSize.sp,
fontFamily = fontFamilyMeasureFont
),
constraints = ParagraphConstraints(width = paragraphWidth)
)
assertThat(paragraph.lineCount).isEqualTo(2)
assertThat(paragraph.getHorizontalPosition(0, true)).isEqualTo(indent * fontSize)
}
}
@Test
fun textIndent_inEm_onRestLine() {
with(defaultDensity) {
val text = "abcdef"
val fontSize = 20f
val indent = 1.5f
val paragraphWidth = "abcd".length * fontSize
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
textIndent = TextIndent(restLine = indent.em),
fontSize = fontSize.sp,
fontFamily = fontFamilyMeasureFont
),
constraints = ParagraphConstraints(width = paragraphWidth)
)
assertThat(paragraph.lineCount).isEqualTo(2)
// check the position of the first character in second line: "e" should be indented
assertThat(paragraph.getHorizontalPosition(4, true)).isEqualTo(indent * fontSize)
}
}
@Test
fun testAnnotatedString_fontFamily_changesMeasurement() {
with(defaultDensity) {
val text = "ad"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
// custom 100 regular font has b as the wide glyph
// custom 200 regular font has d as the wide glyph
val spanStyle = SpanStyle(fontFamily = fontFamilyCustom200)
// a is rendered in paragraphStyle font (custom 100), it will not have wide glyph
// d is rendered in defaultSpanStyle font (custom 200), and it will be wide glyph
val expectedWidth = fontSizeInPx + fontSizeInPx * 3
val paragraph = simpleParagraph(
text = text,
spanStyles = listOf(
AnnotatedString.Range(spanStyle, "a".length, text.length)
),
style = TextStyle(
fontSize = fontSize,
fontFamily = fontFamilyCustom100
)
)
assertThat(paragraph.lineCount).isEqualTo(1)
assertThat(paragraph.getLineWidth(0)).isEqualTo(expectedWidth)
}
}
@Test
fun testAnnotatedString_fontFeature_turnOffKern() {
with(defaultDensity) {
val text = "AaAa"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
// This fontFeatureSetting turns off the kerning
val spanStyle = SpanStyle(fontFeatureSettings = "\"kern\" 0")
val paragraph = simpleParagraph(
text = text,
spanStyles = listOf(
AnnotatedString.Range(spanStyle, 0, "aA".length)
),
style = TextStyle(
fontSize = fontSize,
fontFamily = fontFamilyKernFont
)
)
// Two characters are kerning, so minus 0.4 * fontSize
val expectedWidth = text.length * fontSizeInPx - 0.4f * fontSizeInPx
assertThat(paragraph.lineCount).isEqualTo(1)
assertThat(paragraph.getLineWidth(0)).isEqualTo(expectedWidth)
}
}
@Test
fun testAnnotatedString_shadow() {
with(defaultDensity) {
val text = "abcde"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val paragraphWidth = fontSizeInPx * text.length
val spanStyle = SpanStyle(
shadow = Shadow(
Color(0xFF00FF00),
Offset(1f, 2f),
3.0f
)
)
val paragraphShadow = simpleParagraph(
text = text,
spanStyles = listOf(
AnnotatedString.Range(spanStyle, 0, text.length)
),
constraints = ParagraphConstraints(width = paragraphWidth)
)
val paragraph = simpleParagraph(
text = text,
constraints = ParagraphConstraints(width = paragraphWidth)
)
assertThat(paragraphShadow.bitmap()).isNotEqualToBitmap(paragraph.bitmap())
}
}
@Test
fun testDefaultSpanStyle_setColor() {
with(defaultDensity) {
val text = "abc"
// FontSize doesn't matter here, but it should be big enough for bitmap comparison.
val fontSize = 100.sp
val fontSizeInPx = fontSize.toPx()
val paragraphWidth = fontSizeInPx * text.length
val paragraphWithoutColor = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
constraints = ParagraphConstraints(paragraphWidth)
)
val paragraphWithColor = simpleParagraph(
text = text,
style = TextStyle(
color = Color.Red,
fontSize = fontSize
),
constraints = ParagraphConstraints(paragraphWidth)
)
assertThat(paragraphWithColor.bitmap())
.isNotEqualToBitmap(paragraphWithoutColor.bitmap())
}
}
@Test
fun testDefaultSpanStyle_setLetterSpacing() {
with(defaultDensity) {
val text = "abc"
// FontSize doesn't matter here, but it should be big enough for bitmap comparison.
val fontSize = 100.sp
val fontSizeInPx = fontSize.toPx()
val letterSpacing = 1f
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
letterSpacing = letterSpacing.em,
fontSize = fontSize
)
)
assertThat(paragraph.getLineRight(0))
.isEqualTo(fontSizeInPx * (1 + letterSpacing) * text.length)
}
}
@Test
fun testGetPathForRange_singleLine() {
with(defaultDensity) {
val text = "abc"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
)
val expectedPath = Path()
val lineLeft = paragraph.getLineLeft(0)
val lineRight = paragraph.getLineRight(0)
expectedPath.addRect(
Rect(
lineLeft,
0f,
lineRight - fontSizeInPx,
fontSizeInPx
)
)
// Select "ab"
val actualPath = paragraph.getPathForRange(0, 2)
val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
assertThat(diff).isEqualTo(Rect.zero)
}
}
@Test
fun testGetPathForRange_multiLines() {
with(defaultDensity) {
val text = "abc\nabc"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
)
val expectedPath = Path()
val firstLineLeft = paragraph.getLineLeft(0)
val secondLineLeft = paragraph.getLineLeft(1)
val firstLineRight = paragraph.getLineRight(0)
val secondLineRight = paragraph.getLineRight(1)
expectedPath.addRect(
Rect(
firstLineLeft + fontSizeInPx,
0f,
firstLineRight,
fontSizeInPx
)
)
expectedPath.addRect(
Rect(
secondLineLeft,
fontSizeInPx,
secondLineRight - fontSizeInPx,
paragraph.height
)
)
// Select "bc\nab"
val actualPath = paragraph.getPathForRange(1, 6)
val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
assertThat(diff).isEqualTo(Rect.zero)
}
}
@Test
fun testGetPathForRange_Bidi() {
with(defaultDensity) {
val textLTR = "Hello"
val textRTL = "שלום"
val text = textLTR + textRTL
val selectionLTRStart = 2
val selectionRTLEnd = 2
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
)
val expectedPath = Path()
val lineLeft = paragraph.getLineLeft(0)
val lineRight = paragraph.getLineRight(0)
expectedPath.addRect(
Rect(
lineLeft + selectionLTRStart * fontSizeInPx,
0f,
lineLeft + textLTR.length * fontSizeInPx,
fontSizeInPx
)
)
expectedPath.addRect(
Rect(
lineRight - selectionRTLEnd * fontSizeInPx,
0f,
lineRight,
fontSizeInPx
)
)
// Select "llo..של"
val actualPath =
paragraph.getPathForRange(selectionLTRStart, textLTR.length + selectionRTLEnd)
val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
assertThat(diff).isEqualTo(Rect.zero)
}
}
@Test
fun testGetPathForRange_Start_Equals_End_Returns_Empty_Path() {
val text = "abc"
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontFamily = fontFamilyMeasureFont,
fontSize = 20.sp
)
)
val actualPath = paragraph.getPathForRange(1, 1)
assertThat(actualPath.getBounds()).isEqualTo(Rect.zero)
}
@Test
fun testGetPathForRange_Empty_Text() {
val text = ""
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontFamily = fontFamilyMeasureFont,
fontSize = 20.sp
)
)
val actualPath = paragraph.getPathForRange(0, 0)
assertThat(actualPath.getBounds()).isEqualTo(Rect.zero)
}
@Test
fun testGetPathForRange_Surrogate_Pair_Start_Middle_Second_Character_Selected() {
with(defaultDensity) {
val text = "\uD834\uDD1E\uD834\uDD1F"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
)
val expectedPath = Path()
val lineRight = paragraph.getLineRight(0)
expectedPath.addRect(Rect(lineRight / 2, 0f, lineRight, fontSizeInPx))
// Try to select "\uDD1E\uD834\uDD1F", only "\uD834\uDD1F" is selected.
val actualPath = paragraph.getPathForRange(1, text.length)
val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
assertThat(diff).isEqualTo(Rect.zero)
}
}
@Test
fun testGetPathForRange_Surrogate_Pair_End_Middle_Second_Character_Selected() {
with(defaultDensity) {
val text = "\uD834\uDD1E\uD834\uDD1F"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
)
val expectedPath = Path()
val lineRight = paragraph.getLineRight(0)
expectedPath.addRect(Rect(lineRight / 2, 0f, lineRight, fontSizeInPx))
// Try to select "\uDD1E\uD834", actually "\uD834\uDD1F" is selected.
val actualPath = paragraph.getPathForRange(1, text.length - 1)
val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
assertThat(diff).isEqualTo(Rect.zero)
}
}
@Test
fun testGetPathForRange_Surrogate_Pair_Start_Middle_End_Same_Character_Returns_Line_Segment() {
with(defaultDensity) {
val text = "\uD834\uDD1E\uD834\uDD1F"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
)
val expectedPath = Path()
val lineRight = paragraph.getLineRight(0)
expectedPath.addRect(Rect(lineRight / 2, 0f, lineRight / 2, fontSizeInPx))
// Try to select "\uDD1E", get vertical line segment after this character.
val actualPath = paragraph.getPathForRange(1, 2)
val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
assertThat(diff).isEqualTo(Rect.zero)
}
}
@Test
fun testGetPathForRange_Emoji_Sequence() {
with(defaultDensity) {
val text = "\u1F600\u1F603\u1F604\u1F606"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
)
val expectedPath = Path()
val lineLeft = paragraph.getLineLeft(0)
val lineRight = paragraph.getLineRight(0)
expectedPath.addRect(
Rect(
lineLeft + fontSizeInPx,
0f,
lineRight - fontSizeInPx,
fontSizeInPx
)
)
// Select "\u1F603\u1F604"
val actualPath = paragraph.getPathForRange(1, text.length - 1)
val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
assertThat(diff).isEqualTo(Rect.zero)
}
}
@Test
fun testGetPathForRange_Unicode_200D_Return_Line_Segment() {
with(defaultDensity) {
val text = "\u200D"
val fontSize = 20.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
)
val expectedPath = Path()
val lineLeft = paragraph.getLineLeft(0)
val lineRight = paragraph.getLineRight(0)
expectedPath.addRect(Rect(lineLeft, 0f, lineRight, fontSizeInPx))
val actualPath = paragraph.getPathForRange(0, 1)
assertThat(lineLeft).isEqualTo(lineRight)
val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
assertThat(diff).isEqualTo(Rect.zero)
}
}
@Test
fun testGetPathForRange_Unicode_2066_Return_Line_Segment() {
with(defaultDensity) {
val text = "\u2066"
val fontSize = 20f.sp
val fontSizeInPx = fontSize.toPx()
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
)
val expectedPath = Path()
val lineLeft = paragraph.getLineLeft(0)
val lineRight = paragraph.getLineRight(0)
expectedPath.addRect(Rect(lineLeft, 0f, lineRight, fontSizeInPx))
val actualPath = paragraph.getPathForRange(0, 1)
assertThat(lineLeft).isEqualTo(lineRight)
val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
assertThat(diff).isEqualTo(Rect.zero)
}
}
@Test
fun testGetWordBoundary() {
val text = "abc def"
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontFamily = fontFamilyMeasureFont,
fontSize = 20.sp
)
)
val result = paragraph.getWordBoundary(text.indexOf('a'))
assertThat(result.start).isEqualTo(text.indexOf('a'))
assertThat(result.end).isEqualTo(text.indexOf(' '))
}
@Test
fun testGetWordBoundary_Bidi() {
val text = "abc \u05d0\u05d1\u05d2 def"
val paragraph = simpleParagraph(
text = text,
style = TextStyle(
fontFamily = fontFamilyMeasureFont,
fontSize = 20.sp
)
)
val resultEnglish = paragraph.getWordBoundary(text.indexOf('a'))
val resultHebrew = paragraph.getWordBoundary(text.indexOf('\u05d1'))
assertThat(resultEnglish.start).isEqualTo(text.indexOf('a'))
assertThat(resultEnglish.end).isEqualTo(text.indexOf(' '))
assertThat(resultHebrew.start).isEqualTo(text.indexOf('\u05d0'))
assertThat(resultHebrew.end).isEqualTo(text.indexOf('\u05d2') + 1)
}
@Test
fun test_finalFontSizeChangesWithDensity() {
val text = "a"
val fontSize = 20.sp
val densityMultiplier = 2f
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
density = Density(density = 1f, fontScale = 1f)
)
val doubleFontSizeParagraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
density = Density(density = 1f, fontScale = densityMultiplier)
)
assertThat(doubleFontSizeParagraph.maxIntrinsicWidth)
.isEqualTo(paragraph.maxIntrinsicWidth * densityMultiplier)
assertThat(doubleFontSizeParagraph.height).isEqualTo(paragraph.height * densityMultiplier)
}
@Test
fun minInstrinsicWidth_includes_white_space() {
with(defaultDensity) {
val fontSize = 12.sp
val text = "b "
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize)
)
val expectedWidth = text.length * fontSize.toPx()
assertThat(paragraph.minIntrinsicWidth).isEqualTo(expectedWidth)
}
}
@Test
fun minInstrinsicWidth_returns_longest_word_width() {
with(defaultDensity) {
// create words with length 1, 2, 3... 50; and append all with space.
val maxWordLength = 50
val text = (1..maxWordLength).fold("") { string, next ->
string + "a".repeat(next) + " "
}
val fontSize = 12.sp
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize)
)
// +1 is for the white space
val expectedWidth = (maxWordLength + 1) * fontSize.toPx()
assertThat(paragraph.minIntrinsicWidth).isEqualTo(expectedWidth)
}
}
@Test
fun minInstrinsicWidth_withStyledText() {
with(defaultDensity) {
val text = "a bb ccc"
val fontSize = 12.sp
val styledFontSize = fontSize * 2
val paragraph = simpleParagraph(
text = text,
style = TextStyle(fontSize = fontSize),
spanStyles = listOf(
AnnotatedString.Range(
SpanStyle(fontSize = styledFontSize), "a".length, "a bb ".length
)
)
)
val expectedWidth = "bb ".length * styledFontSize.toPx()
assertThat(paragraph.minIntrinsicWidth).isEqualTo(expectedWidth)
}
}
@Test(expected = AssertionError::class)
fun getPathForRange_throws_exception_if_start_larger_than_end() {
val text = "ab"
val textStart = 0
val textEnd = text.length
val paragraph = simpleParagraph(text = text)
paragraph.getPathForRange(textEnd, textStart)
}
@Test(expected = AssertionError::class)
fun getPathForRange_throws_exception_if_start_is_smaller_than_zero() {
val text = "ab"
val textStart = 0
val textEnd = text.length
val paragraph = simpleParagraph(text = text)
paragraph.getPathForRange(textStart - 2, textEnd - 1)
}
@Test(expected = AssertionError::class)
fun getPathForRange_throws_exception_if_end_is_larger_than_text_length() {
val text = "ab"
val textStart = 0
val textEnd = text.length
val paragraph = simpleParagraph(text = text)
paragraph.getPathForRange(textStart, textEnd + 1)
}
@Test
fun createParagraph_with_ParagraphIntrinsics() {
with(defaultDensity) {
val text = "abc"
val fontSize = 14.sp
val fontSizeInPx = fontSize.toPx()
val paragraphIntrinsics = ParagraphIntrinsics(
text = text,
style = TextStyle(
fontSize = fontSize,
fontFamily = fontFamilyMeasureFont
),
spanStyles = listOf(),
density = defaultDensity,
resourceLoader = TestFontResourceLoader(context)
)
val paragraph = Paragraph(
paragraphIntrinsics = paragraphIntrinsics,
constraints = ParagraphConstraints(fontSizeInPx * text.length)
)
assertThat(paragraph.maxIntrinsicWidth).isEqualTo(paragraphIntrinsics.maxIntrinsicWidth)
assertThat(paragraph.width).isEqualTo(fontSizeInPx * text.length)
}
}
@Test(expected = IllegalArgumentException::class)
fun negativeMaxLines_throwsException() {
simpleParagraph(
text = "",
maxLines = -1,
constraints = ParagraphConstraints(Float.MAX_VALUE)
)
}
private fun simpleParagraph(
text: String = "",
style: TextStyle? = null,
maxLines: Int = Int.MAX_VALUE,
ellipsis: Boolean = false,
spanStyles: List<AnnotatedString.Range<SpanStyle>> = listOf(),
density: Density? = null,
constraints: ParagraphConstraints = ParagraphConstraints(Float.MAX_VALUE)
): Paragraph {
return Paragraph(
text = text,
spanStyles = spanStyles,
style = TextStyle(
fontFamily = fontFamilyMeasureFont
).merge(style),
maxLines = maxLines,
ellipsis = ellipsis,
constraints = constraints,
density = density ?: defaultDensity,
resourceLoader = TestFontResourceLoader(context)
)
}
}