| /* |
| * 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) |
| ) |
| } |
| } |