[go: nahoru, domu]

blob: d8a13ca775edf9fec4e44c7e05f1d0b49092c767 [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.ui.engine.text
import androidx.core.os.BuildCompat
import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.ui.core.px
import androidx.ui.engine.geometry.Offset
import androidx.ui.engine.geometry.Rect
import androidx.ui.engine.text.FontTestData.Companion.BASIC_KERN_FONT
import androidx.ui.engine.text.FontTestData.Companion.BASIC_MEASURE_FONT
import androidx.ui.engine.text.FontTestData.Companion.FONT_100_REGULAR
import androidx.ui.engine.text.FontTestData.Companion.FONT_200_REGULAR
import androidx.ui.engine.text.font.FontFamily
import androidx.ui.engine.text.font.asFontFamily
import androidx.ui.engine.window.Locale
import androidx.ui.matchers.equalToBitmap
import androidx.ui.graphics.Color
import androidx.ui.painting.Path
import androidx.ui.painting.PathOperation
import androidx.ui.painting.Shadow
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.not
import org.junit.Assert.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@RunWith(JUnit4::class)
@SmallTest
class ParagraphIntegrationTest {
// TODO(Migration/haoyuchang): These native calls should be removed after the
// counterparts are implemented in crane.
private lateinit var fontFamilyMeasureFont: FontFamily
private lateinit var fontFamilyKernFont: FontFamily
private lateinit var fontFamilyCustom100: FontFamily
private lateinit var fontFamilyCustom200: FontFamily
@Before
fun setup() {
// This sample font provides the following features:
// 1. The width of most of visible characters equals to font size.
// 2. The LTR/RTL characters are rendered as ▶/◀.
// 3. The fontMetrics passed to TextPaint has descend - ascend equal to 1.2 * fontSize.
fontFamilyMeasureFont = BASIC_MEASURE_FONT.asFontFamily()
fontFamilyMeasureFont.context = InstrumentationRegistry.getInstrumentation().context
// The kern_font provides the following features:
// 1. Characters from A to Z are rendered as ▲ while a to z are rendered as ▼.
// 2. When kerning is off, the width of each character is equal to font size.
// 3. When kerning is on, it will reduce the space between two characters by 0.4 * width.
fontFamilyKernFont = BASIC_KERN_FONT.asFontFamily()
fontFamilyKernFont.context = InstrumentationRegistry.getInstrumentation().context
fontFamilyCustom100 = FONT_100_REGULAR.asFontFamily()
fontFamilyCustom200 = FONT_200_REGULAR.asFontFamily()
fontFamilyCustom100.context = fontFamilyMeasureFont.context
fontFamilyCustom200.context = fontFamilyMeasureFont.context
}
@Test
fun empty_string() {
val fontSize = 50.0f
val text = StringBuilder("")
val paragraph = simpleParagraph(text = text, fontSize = fontSize)
paragraph.layout(ParagraphConstraints(width = 100.0f))
assertThat(paragraph.width, equalTo(100.0f))
assertThat(paragraph.height, equalTo(fontSize))
// defined in sample_font
assertThat(paragraph.baseline, equalTo(fontSize * 0.8f))
assertThat(paragraph.maxIntrinsicWidth, equalTo(0.0f))
assertThat(paragraph.minIntrinsicWidth, equalTo(0.0f))
// TODO(Migration/siyamed): no baseline query per line?
// TODO(Migration/siyamed): no line count?
}
@Test
fun single_line_default_values() {
val fontSize = 50.0f
for (text in arrayOf("xyz", "\u05D0\u05D1\u05D2")) {
val paragraph = simpleParagraph(text = text, fontSize = fontSize)
// width greater than text width - 150
paragraph.layout(ParagraphConstraints(width = 200.0f))
assertThat(text, paragraph.width, equalTo(200.0f))
assertThat(text, paragraph.height, equalTo(fontSize))
// defined in sample_font
assertThat(text, paragraph.baseline, equalTo(fontSize * 0.8f))
assertThat(text, paragraph.maxIntrinsicWidth, equalTo(fontSize * text.length))
assertThat(text, paragraph.minIntrinsicWidth, equalTo(0.0f))
}
}
@Test
fun line_break_default_values() {
val fontSize = 50.0f
for (text in arrayOf("abcdef", "\u05D0\u05D1\u05D2\u05D3\u05D4\u05D5")) {
val paragraph = simpleParagraph(text = text, fontSize = fontSize)
// 3 chars width
paragraph.layout(ParagraphConstraints(width = 3 * fontSize))
// 3 chars
assertThat(text, paragraph.width, equalTo(3 * fontSize))
// 2 lines, 1 line gap
assertThat(text, paragraph.height, equalTo(2 * fontSize + fontSize / 5.0f))
// defined in sample_font
assertThat(text, paragraph.baseline, equalTo(fontSize * 0.8f))
assertThat(text, paragraph.maxIntrinsicWidth, equalTo(fontSize * text.length))
assertThat(text, paragraph.minIntrinsicWidth, equalTo(0.0f))
}
}
@Test
fun newline_default_values() {
val fontSize = 50.0f
for (text in arrayOf("abc\ndef", "\u05D0\u05D1\u05D2\n\u05D3\u05D4\u05D5")) {
val paragraph = simpleParagraph(text = text, fontSize = fontSize)
// 3 chars width
paragraph.layout(ParagraphConstraints(width = 3 * fontSize))
// 3 chars
assertThat(text, paragraph.width, equalTo(3 * fontSize))
// 2 lines, 1 line gap
assertThat(text, paragraph.height, equalTo(2 * fontSize + fontSize / 5.0f))
// defined in sample_font
assertThat(text, paragraph.baseline, equalTo(fontSize * 0.8f))
assertThat(text, paragraph.maxIntrinsicWidth, equalTo(fontSize * text.indexOf("\n")))
assertThat(text, paragraph.minIntrinsicWidth, equalTo(0.0f))
}
}
@Test
fun newline_and_line_break_default_values() {
val fontSize = 50.0f
for (text in arrayOf("abc\ndef", "\u05D0\u05D1\u05D2\n\u05D3\u05D4\u05D5")) {
val paragraph = simpleParagraph(text = text, fontSize = fontSize)
// 2 chars width
paragraph.layout(ParagraphConstraints(width = 2 * fontSize))
// 2 chars
assertThat(text, paragraph.width, equalTo(2 * fontSize))
// 4 lines, 3 line gaps
assertThat(text, paragraph.height, equalTo(4 * fontSize + 3 * fontSize / 5.0f))
// defined in sample_font
assertThat(text, paragraph.baseline, equalTo(fontSize * 0.8f))
assertThat(text, paragraph.maxIntrinsicWidth, equalTo(fontSize * text.indexOf("\n")))
assertThat(text, paragraph.minIntrinsicWidth, equalTo(0.0f))
}
}
@Test
fun getPositionForOffset_ltr() {
val text = "abc"
val fontSize = 50.0f
val paragraph = simpleParagraph(text = text, fontSize = fontSize)
paragraph.layout(ParagraphConstraints(width = text.length * fontSize))
// test positions that are 1, fontSize+1, 2fontSize+1 which maps to chars 0, 1, 2 ...
for (i in 0..text.length) {
val offset = Offset(i * fontSize + 1, fontSize / 2)
val position = paragraph.getPositionForOffset(offset)
assertThat(
"position at index $i, offset $offset does not match",
position,
equalTo(TextPosition(i, TextAffinity.upstream))
)
}
}
@Test
fun getPositionForOffset_rtl() {
val text = "\u05D0\u05D1\u05D2"
val fontSize = 50.0f
val paragraph = simpleParagraph(text = text, fontSize = fontSize)
paragraph.layout(ParagraphConstraints(width = text.length * fontSize))
// test positions that are 1, fontSize+1, 2fontSize+1 which maps to chars .., 2, 1, 0
for (i in 0..text.length) {
val offset = Offset(i * fontSize + 1, fontSize / 2)
val position = paragraph.getPositionForOffset(offset)
assertThat(
"position at index $i, offset $offset does not match",
position,
equalTo(TextPosition(text.length - i, TextAffinity.upstream))
)
}
}
@Test
fun getPositionForOffset_ltr_multiline() {
val firstLine = "abc"
val secondLine = "def"
val text = firstLine + secondLine
val fontSize = 50.0f
val paragraph = simpleParagraph(text = text, fontSize = fontSize)
paragraph.layout(ParagraphConstraints(width = firstLine.length * fontSize))
// 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 offset = Offset(i * fontSize + 1, fontSize * 1.5f)
val position = paragraph.getPositionForOffset(offset)
assertThat(
"position at index $i, offset $offset, second line does not match",
position,
equalTo(TextPosition(i + firstLine.length, TextAffinity.upstream))
)
}
}
@Test
fun getPositionForOffset_rtl_multiline() {
val firstLine = "\u05D0\u05D1\u05D2"
val secondLine = "\u05D3\u05D4\u05D5"
val text = firstLine + secondLine
val fontSize = 50.0f
val paragraph = simpleParagraph(text = text, fontSize = fontSize)
paragraph.layout(ParagraphConstraints(width = firstLine.length * fontSize))
// 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 offset = Offset(i * fontSize + 1, fontSize * 1.5f)
val position = paragraph.getPositionForOffset(offset)
assertThat(
"position at index $i, offset $offset, second line does not match",
position,
equalTo(TextPosition(text.length - i, TextAffinity.upstream))
)
}
}
@Test
fun getPositionForOffset_ltr_width_outOfBounds() {
val text = "abc"
val fontSize = 50.0f
val paragraph = simpleParagraph(text = text, fontSize = fontSize)
paragraph.layout(ParagraphConstraints(width = text.length * fontSize))
// greater than width
var offset = Offset(fontSize * text.length * 2, fontSize / 2)
var position = paragraph.getPositionForOffset(offset)
assertThat(position, equalTo(TextPosition(text.length, TextAffinity.upstream)))
// negative
offset = Offset(-1 * fontSize, fontSize / 2)
position = paragraph.getPositionForOffset(offset)
assertThat(position, equalTo(TextPosition(0, TextAffinity.upstream)))
}
@Test
fun getPositionForOffset_ltr_height_outOfBounds() {
val text = "abc"
val fontSize = 50.0f
val paragraph = simpleParagraph(text = text, fontSize = fontSize)
paragraph.layout(ParagraphConstraints(width = text.length * fontSize))
// greater than height
var offset = Offset(fontSize / 2, fontSize * text.length * 2)
var position = paragraph.getPositionForOffset(offset)
assertThat(position, equalTo(TextPosition(0, TextAffinity.upstream)))
// negative
offset = Offset(fontSize / 2, -1 * fontSize)
position = paragraph.getPositionForOffset(offset)
assertThat(position, equalTo(TextPosition(0, TextAffinity.upstream)))
}
@Test
fun getCaretForTextPosition_ltr_singleLine() {
val text = "abc"
val fontSize = 50.0f
val paragraph = simpleParagraph(text = text, fontSize = fontSize)
paragraph.layout(ParagraphConstraints(width = text.length * fontSize))
// test positions that are 0, 1, 2 ... which maps to chars 0, 1, 2 ...
for (i in 0..text.length) {
val textPosition = TextPosition(i, TextAffinity.upstream)
val caret = paragraph.getCaretForTextPosition(textPosition)
assertThat(caret.first.dx, equalTo(i * fontSize))
assertThat(caret.first.dy, equalTo(0f))
assertThat(caret.second.dx, equalTo(i * fontSize))
assertThat(caret.second.dy, equalTo(fontSize))
}
}
@Test
fun getCaretForTextPosition_rtl_singleLine() {
val text = "\u05D0\u05D1\u05D2"
val fontSize = 50.0f
val paragraph = simpleParagraph(text = text, fontSize = fontSize)
paragraph.layout(ParagraphConstraints(width = text.length * fontSize))
// test positions that are 0, 1, 2 which maps to chars .., 2, 1, 0
for (i in 0..text.length) {
val textPosition = TextPosition(i, TextAffinity.upstream)
val caret = paragraph.getCaretForTextPosition(textPosition)
assertThat(caret.first.dx, equalTo((text.length - i) * fontSize))
assertThat(caret.first.dy, equalTo(0f))
assertThat(caret.second.dx, equalTo((text.length - i) * fontSize))
assertThat(caret.second.dy, equalTo(fontSize))
}
}
@Test
fun getCaretForTextPosition_ltr_multiLines() {
val firstLine = "abc"
val secondLine = "def"
val text = firstLine + secondLine
val fontSize = 50.0f
val paragraph = simpleParagraph(text = text, fontSize = fontSize)
paragraph.layout(ParagraphConstraints(width = firstLine.length * fontSize))
// test positions are 3, 4, 5 and always on the second line
// which maps to chars 3, 4, 5
for (i in 0..secondLine.length) {
val textPosition = TextPosition(i + firstLine.length, TextAffinity.upstream)
val caret = paragraph.getCaretForTextPosition(textPosition)
assertThat(caret.first.dx, equalTo(i * fontSize))
assertThat(caret.first.dy, equalTo(fontSize))
assertThat(caret.second.dx, equalTo(i * fontSize))
assertThat(caret.second.dy, equalTo((2f + 1 / 5f) * fontSize))
}
}
@Test
fun getCaretForTextPosition_rtl_multiLines() {
val firstLine = "\u05D0\u05D1\u05D2"
val secondLine = "\u05D3\u05D4\u05D5"
val text = firstLine + secondLine
val fontSize = 50.0f
val paragraph = simpleParagraph(text = text, fontSize = fontSize)
paragraph.layout(ParagraphConstraints(width = firstLine.length * fontSize))
// test positions are 3, 4, 5 and always on the second line
// which maps to chars 5, 4, 3
for (i in 0..secondLine.length) {
val textPosition = TextPosition(i + firstLine.length, TextAffinity.upstream)
val caret = paragraph.getCaretForTextPosition(textPosition)
assertThat(caret.first.dx, equalTo((secondLine.length - i) * fontSize))
assertThat(caret.first.dy, equalTo(fontSize))
assertThat(caret.second.dx, equalTo((secondLine.length - i) * fontSize))
assertThat(caret.second.dy, equalTo((2f + 1 / 5f) * fontSize))
}
}
@Test
fun getCaretForTextPosition_ltr_textPosition_negative_return_zero() {
val text = "abc"
val fontSize = 50.0f
val paragraph = simpleParagraph(text = text, fontSize = fontSize)
paragraph.layout(ParagraphConstraints(width = text.length * fontSize))
val textPosition = TextPosition(-1, TextAffinity.upstream)
val caret = paragraph.getCaretForTextPosition(textPosition)
assertThat(caret.first.dx, equalTo(0f))
assertThat(caret.first.dy, equalTo(0f))
assertThat(caret.second.dx, equalTo(0f))
assertThat(caret.second.dy, equalTo(fontSize))
}
@Test(expected = java.lang.IndexOutOfBoundsException::class)
fun getCaretForTextPosition_ltr_textPosition_larger_than_length_throw_exception() {
val text = "abc"
val fontSize = 50.0f
val paragraph = simpleParagraph(text = text, fontSize = fontSize)
paragraph.layout(ParagraphConstraints(width = text.length * fontSize))
val textPosition = TextPosition(text.length + 1, TextAffinity.upstream)
paragraph.getCaretForTextPosition(textPosition)
}
@Test
fun getBoundingBoxForTextPosition_ltr_singleLine() {
val text = "abc"
val fontSize = 50.0f
val paragraph = simpleParagraph(text = text, fontSize = fontSize)
paragraph.layout(ParagraphConstraints(width = text.length * fontSize))
// test positions that are 0, 1, 2 ... which maps to chars 0, 1, 2 ...
for (i in 0..text.length - 1) {
val textPosition = TextPosition(i, TextAffinity.upstream)
val box = paragraph.getBoundingBoxForTextPosition(textPosition)
assertThat(box.left, equalTo(i * fontSize))
assertThat(box.right, equalTo((i + 1) * fontSize))
assertThat(box.top, equalTo(0f))
assertThat(box.bottom, equalTo(fontSize))
}
}
@Test
fun getBoundingBoxForTextPosition_ltr_multiLines() {
val firstLine = "abc"
val secondLine = "def"
val text = firstLine + secondLine
val fontSize = 50.0f
val paragraph = simpleParagraph(text = text, fontSize = fontSize)
paragraph.layout(ParagraphConstraints(width = firstLine.length * fontSize))
// test positions are 3, 4, 5 and always on the second line
// which maps to chars 3, 4, 5
for (i in 0..secondLine.length - 1) {
val textPosition = TextPosition(i + firstLine.length, TextAffinity.upstream)
val box = paragraph.getBoundingBoxForTextPosition(textPosition)
assertThat(box.left, equalTo(i * fontSize))
assertThat(box.right, equalTo((i + 1) * fontSize))
assertThat(box.top, equalTo(fontSize))
assertThat(box.bottom, equalTo((2f + 1 / 5f) * fontSize))
}
}
@Test
fun getBoundingBoxForTextPosition_ltr_textPosition_negative() {
val text = "abc"
val fontSize = 50.0f
val paragraph = simpleParagraph(text = text, fontSize = fontSize)
paragraph.layout(ParagraphConstraints(width = text.length * fontSize))
val textPosition = TextPosition(-1, TextAffinity.upstream)
val box = paragraph.getBoundingBoxForTextPosition(textPosition)
assertThat(box.left, equalTo(0f))
assertThat(box.right, equalTo(0f))
assertThat(box.top, equalTo(0f))
assertThat(box.bottom, equalTo(fontSize))
}
@Test(expected = java.lang.IndexOutOfBoundsException::class)
fun getBoundingBoxForTextPosition_ltr_textPosition_larger_than_length_throw_exception() {
val text = "abc"
val fontSize = 50.0f
val paragraph = simpleParagraph(text = text, fontSize = fontSize)
paragraph.layout(ParagraphConstraints(width = text.length * fontSize))
val textPosition = TextPosition(text.length + 1, TextAffinity.upstream)
paragraph.getBoundingBoxForTextPosition(textPosition)
}
@Test
fun locale_withCJK_shouldNotDrawSame() {
val text = "\u82B1"
val fontSize = 10.0f
val locales = arrayOf(
// duplicate ja is on purpose
Locale(_languageCode = "ja"),
Locale(_languageCode = "ja"),
Locale(_languageCode = "zh", _countryCode = "CN"),
Locale(_languageCode = "zh", _countryCode = "TW")
)
val bitmaps = locales.map { locale ->
val paragraph = Paragraph(
text = StringBuilder(text),
textStyles = listOf(),
paragraphStyle = ParagraphStyle(
fontSize = fontSize,
locale = locale
)
)
// just have 10x font size to have a bitmap
paragraph.layout(ParagraphConstraints(width = fontSize * 10))
paragraph.bitmap()
}
assertThat(bitmaps[0], equalToBitmap(bitmaps[1]))
assertThat(bitmaps[1], not(equalToBitmap(bitmaps[2])))
assertThat(bitmaps[1], not(equalToBitmap(bitmaps[3])))
// this does not work on API 21
// assertThat(bitmaps[2], not(equalToBitmap(bitmaps[3])))
}
@Test
fun locale_isDefaultLocaleIfNotProvided() {
val text = "abc"
val paragraph = Paragraph(
text = StringBuilder(text),
textStyles = listOf(),
paragraphStyle = ParagraphStyle()
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
assertThat(
paragraph.paragraphImpl.textLocale.toLanguageTag(),
equalTo(java.util.Locale.getDefault().toLanguageTag())
)
}
@Test
fun locale_isSetOnParagraphImpl_enUS() {
val locale = Locale(_languageCode = "en", _countryCode = "US")
val text = "abc"
val paragraph = Paragraph(
text = StringBuilder(text),
textStyles = listOf(),
paragraphStyle = ParagraphStyle(
locale = locale
)
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
assertThat(paragraph.paragraphImpl.textLocale.toLanguageTag(), equalTo("en-US"))
}
@Test
fun locale_isSetOnParagraphImpl_jpJP() {
val locale = Locale(_languageCode = "ja", _countryCode = "JP")
val text = "abc"
val paragraph = Paragraph(
text = StringBuilder(text),
textStyles = listOf(),
paragraphStyle = ParagraphStyle(
locale = locale
)
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
assertThat(paragraph.paragraphImpl.textLocale.toLanguageTag(), equalTo("ja-JP"))
}
@Test
fun locale_noCountryCode_isSetOnParagraphImpl() {
val locale = Locale(_languageCode = "ja")
val text = "abc"
val paragraph = Paragraph(
text = StringBuilder(text),
textStyles = listOf(),
paragraphStyle = ParagraphStyle(
locale = locale
)
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
assertThat(paragraph.paragraphImpl.textLocale.toLanguageTag(), equalTo("ja"))
}
@Test
fun maxLines_withMaxLineEqualsZero() {
val text = "a\na\na"
val fontSize = 100.0f
val maxLines = 0
val paragraph = simpleParagraph(
text = text,
fontSize = fontSize,
maxLines = maxLines
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
assertThat(paragraph.height, equalTo(0f))
}
@Test(expected = java.lang.IllegalArgumentException::class)
fun maxLines_withMaxLineNegative_throwsException() {
val text = "a\na\na"
val fontSize = 100.0f
val maxLines = -1
val paragraph = simpleParagraph(
text = text,
fontSize = fontSize,
maxLines = maxLines
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
}
@Test
fun maxLines_withMaxLineSmallerThanTextLines_clipHeight() {
val text = "a\na\na"
val fontSize = 100.0f
val lineCount = text.lines().size
val maxLines = lineCount
val paragraph = simpleParagraph(
text = text,
fontSize = fontSize,
maxLines = maxLines
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val expectHeight = (lineCount + (lineCount - 1) * 0.2f) * fontSize
assertThat(paragraph.height, equalTo(expectHeight))
}
@Test
fun maxLines_withMaxLineEqualsTextLine() {
val text = "a\na\na"
val fontSize = 100.0f
val maxLines = text.lines().size
val paragraph = simpleParagraph(
text = text,
fontSize = fontSize,
maxLines = maxLines
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val expectHeight = (maxLines + (maxLines - 1) * 0.2f) * fontSize
assertThat(paragraph.height, equalTo(expectHeight))
}
@Test
fun maxLines_withMaxLineGreaterThanTextLines() {
val text = "a\na\na"
val fontSize = 100.0f
val lineCount = text.lines().size
val maxLines = lineCount + 1
val paragraph = simpleParagraph(
text = text,
fontSize = fontSize,
maxLines = maxLines
)
paragraph.layout(ParagraphConstraints(width = 200f))
val expectHeight = (lineCount + (lineCount - 1) * 0.2f) * fontSize
assertThat(paragraph.height, equalTo(expectHeight))
}
@Test
fun didExceedMaxLines_withMaxLinesSmallerThanTextLines_returnsTrue() {
val text = "aaa\naa"
val maxLines = text.lines().size - 1
val paragraph = simpleParagraph(text = text, maxLines = maxLines)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
assertThat(paragraph.didExceedMaxLines, equalTo(true))
}
@Test
fun didExceedMaxLines_withMaxLinesEqualToTextLines_returnsFalse() {
val text = "aaa\naa"
val maxLines = text.lines().size
val paragraph = simpleParagraph(text = text, maxLines = maxLines)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
assertThat(paragraph.didExceedMaxLines, equalTo(false))
}
@Test
fun didExceedMaxLines_withMaxLinesGreaterThanTextLines_returnsFalse() {
val text = "aaa\naa"
val maxLines = text.lines().size + 1
val paragraph = simpleParagraph(text = text, maxLines = maxLines)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
assertThat(paragraph.didExceedMaxLines, equalTo(false))
}
@Test
fun didExceedMaxLines_withMaxLinesSmallerThanTextLines_withLineWrap_returnsTrue() {
val text = "aa"
val fontSize = 50.0f
val maxLines = 1
val paragraph = simpleParagraph(text = text, fontSize = fontSize, maxLines = maxLines)
// One line can only contain 1 character
paragraph.layout(ParagraphConstraints(width = fontSize))
assertThat(paragraph.didExceedMaxLines, equalTo(true))
}
@Test
fun didExceedMaxLines_withMaxLinesEqualToTextLines_withLineWrap_returnsFalse() {
val text = "a"
val fontSize = 50.0f
val maxLines = text.lines().size
val paragraph = simpleParagraph(text = text, fontSize = fontSize, maxLines = maxLines)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
assertThat(paragraph.didExceedMaxLines, equalTo(false))
}
@Test
fun didExceedMaxLines_withMaxLinesGreaterThanTextLines_withLineWrap_returnsFalse() {
val text = "aa"
val maxLines = 3
val fontSize = 50.0f
val paragraph = simpleParagraph(text = text, fontSize = fontSize, maxLines = maxLines)
// One line can only contain 1 character
paragraph.layout(ParagraphConstraints(width = fontSize))
assertThat(paragraph.didExceedMaxLines, equalTo(false))
}
@Test
fun textAlign_defaultValue_alignsStart() {
val textLTR = "aa"
val textRTL = "\u05D0\u05D0"
val fontSize = 20.0f
val paragraphLTR = simpleParagraph(
text = textLTR,
fontSize = fontSize
)
val layoutLTRWidth = (textLTR.length + 2) * fontSize
paragraphLTR.layout(ParagraphConstraints(width = layoutLTRWidth))
val paragraphRTL = simpleParagraph(
text = textRTL,
fontSize = fontSize
)
val layoutRTLWidth = (textRTL.length + 2) * fontSize
paragraphRTL.layout(ParagraphConstraints(width = layoutRTLWidth))
// When textAlign is TextAlign.start, LTR aligns to left, RTL aligns to right.
assertThat(paragraphLTR.paragraphImpl.getLineLeft(0), equalTo(0.0f))
assertThat(paragraphRTL.paragraphImpl.getLineRight(0), equalTo(layoutRTLWidth))
}
@Test
fun textAlign_whenAlignLeft_returnsZeroForGetLineLeft() {
val texts = listOf("aa", "\u05D0\u05D0")
val fontSize = 20.0f
texts.map { text ->
val paragraph = simpleParagraph(
text = text,
textAlign = TextAlign.Left,
fontSize = fontSize
)
val layoutWidth = (text.length + 2) * fontSize
paragraph.layout(ParagraphConstraints(width = layoutWidth))
val paragraphImpl = paragraph.paragraphImpl
assertThat(paragraphImpl.getLineLeft(0), equalTo(0.0f))
}
}
@Test
fun textAlign_whenAlignRight_returnsLayoutWidthForGetLineRight() {
val texts = listOf("aa", "\u05D0\u05D0")
val fontSize = 20.0f
texts.map { text ->
val paragraph = simpleParagraph(
text = text,
textAlign = TextAlign.Right,
fontSize = fontSize
)
val layoutWidth = (text.length + 2) * fontSize
paragraph.layout(ParagraphConstraints(width = layoutWidth))
val paragraphImpl = paragraph.paragraphImpl
assertThat(paragraphImpl.getLineRight(0), equalTo(layoutWidth))
}
}
@Test
fun textAlign_whenAlignCenter_textIsCentered() {
val texts = listOf("aa", "\u05D0\u05D0")
val fontSize = 20.0f
texts.map { text ->
val paragraph = simpleParagraph(
text = text,
textAlign = TextAlign.Center,
fontSize = fontSize
)
val layoutWidth = (text.length + 2) * fontSize
paragraph.layout(ParagraphConstraints(width = layoutWidth))
val textWidth = text.length * fontSize
val paragraphImpl = paragraph.paragraphImpl
assertThat(paragraphImpl.getLineLeft(0),
equalTo(layoutWidth / 2 - textWidth / 2))
assertThat(paragraphImpl.getLineRight(0),
equalTo(layoutWidth / 2 + textWidth / 2))
}
}
@Test
fun textAlign_whenAlignStart_withLTR_returnsZeroForGetLineLeft() {
val text = "aa"
val fontSize = 20.0f
val layoutWidth = (text.length + 2) * fontSize
val paragraph = simpleParagraph(
text = text,
textAlign = TextAlign.Start,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = layoutWidth))
val paragraphImpl = paragraph.paragraphImpl
assertThat(paragraphImpl.getLineLeft(0), equalTo(0.0f))
}
@Test
fun textAlign_whenAlignEnd_withLTR_returnsLayoutWidthForGetLineRight() {
val text = "aa"
val fontSize = 20.0f
val layoutWidth = (text.length + 2) * fontSize
val paragraph = simpleParagraph(
text = text,
textAlign = TextAlign.End,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = layoutWidth))
val paragraphImpl = paragraph.paragraphImpl
assertThat(paragraphImpl.getLineRight(0), equalTo(layoutWidth))
}
@Test
fun textAlign_whenAlignStart_withRTL_returnsLayoutWidthForGetLineRight() {
val text = "\u05D0\u05D0"
val fontSize = 20.0f
val layoutWidth = (text.length + 2) * fontSize
val paragraph = simpleParagraph(
text = text,
textAlign = TextAlign.Start,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = layoutWidth))
val paragraphImpl = paragraph.paragraphImpl
assertThat(paragraphImpl.getLineRight(0), equalTo(layoutWidth))
}
@Test
fun textAlign_whenAlignEnd_withRTL_returnsZeroForGetLineLeft() {
val text = "\u05D0\u05D0"
val fontSize = 20.0f
val layoutWidth = (text.length + 2) * fontSize
val paragraph = simpleParagraph(
text = text,
textAlign = TextAlign.End,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = layoutWidth))
val paragraphImpl = paragraph.paragraphImpl
assertThat(paragraphImpl.getLineLeft(0), equalTo(0.0f))
}
@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() {
val text = "a a a"
val fontSize = 20.0f
val layoutWidth = ("a a".length + 1) * fontSize
val paragraph = simpleParagraph(
text = text,
textAlign = TextAlign.Justify,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = layoutWidth))
val paragraphImpl = paragraph.paragraphImpl
assertThat(paragraphImpl.getLineLeft(0), equalTo(0.0f))
assertThat(paragraphImpl.getLineRight(0), equalTo(layoutWidth))
// Last line should align start
assertThat(paragraphImpl.getLineLeft(1), equalTo(0.0f))
}
@Test
fun textDirection_whenLTR_dotIsOnRight() {
val text = "a.."
val fontSize = 20.0f
val layoutWidth = text.length * fontSize
val paragraph = simpleParagraph(
text = text,
textDirection = TextDirection.Ltr,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = layoutWidth))
// The offset of the last character in display order.
val offset = Offset("a.".length * fontSize + 1, fontSize / 2)
val charIndex = paragraph.getPositionForOffset(offset = offset).offset
assertThat(charIndex, equalTo(2))
}
@Test
fun textDirection_whenRTL_dotIsOnLeft() {
val text = "a.."
val fontSize = 20.0f
val layoutWidth = text.length * fontSize
val paragraph = simpleParagraph(
text = text,
textDirection = TextDirection.Rtl,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = layoutWidth))
// The offset of the first character in display order.
val offset = Offset(fontSize / 2 + 1, fontSize / 2)
val charIndex = paragraph.getPositionForOffset(offset = offset).offset
assertThat(charIndex, equalTo(2))
}
@Test
fun textDirection_whenDefault_withoutStrongChar_directionIsLTR() {
val text = "..."
val fontSize = 20.0f
val layoutWidth = text.length * fontSize
val paragraph = simpleParagraph(
text = text,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = layoutWidth))
for (i in 0..text.length) {
// The offset of the i-th character in display order.
val offset = Offset(i * fontSize + 1, fontSize / 2)
val charIndex = paragraph.getPositionForOffset(offset = offset).offset
assertThat(charIndex, equalTo(i))
}
}
@Test
fun textDirection_whenDefault_withFirstStrongCharLTR_directionIsLTR() {
val text = "a\u05D0."
val fontSize = 20.0f
val layoutWidth = text.length * fontSize
val paragraph = simpleParagraph(
text = text,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = layoutWidth))
for (i in 0 until text.length) {
// The offset of the i-th character in display order.
val offset = Offset(i * fontSize + 1, fontSize / 2)
val charIndex = paragraph.getPositionForOffset(offset = offset).offset
assertThat(charIndex, equalTo(i))
}
}
@Test
fun textDirection_whenDefault_withFirstStrongCharRTL_directionIsRTL() {
val text = "\u05D0a."
val fontSize = 20.0f
val layoutWidth = text.length * fontSize
val paragraph = simpleParagraph(
text = text,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = layoutWidth))
// The first character in display order should be '.'
val offset = Offset(fontSize / 2 + 1, fontSize / 2)
val index = paragraph.getPositionForOffset(offset = offset).offset
assertThat(index, equalTo(2))
}
@Test
fun lineHeight_returnsSameAsGiven() {
val text = "abcdefgh"
val fontSize = 20.0f
// Make the layout 4 lines
val layoutWidth = text.length * fontSize / 4
val lineHeight = 1.5f
val paragraph = simpleParagraph(
text = text,
fontSize = fontSize,
lineHeight = lineHeight
)
paragraph.layout(ParagraphConstraints(width = layoutWidth))
val paragraphImpl = paragraph.paragraphImpl
assertThat(paragraphImpl.lineCount, equalTo(4))
// TODO(Migration/haoyuchang): Due to bug b/120530738, the height of the first line is
// wrong in the framework. Will fix it when the lineHeight in TextSpan is implemented.
for (i in 1 until paragraphImpl.lineCount - 1) {
val actualHeight = paragraphImpl.getLineHeight(i)
// In the sample_font.ttf, the height of the line should be
// fontSize + 0.2f * fontSize(line gap)
assertThat("line number $i", actualHeight, equalTo(1.2f * fontSize * lineHeight))
}
}
@Test
fun lineHeight_hasNoEffectOnLastLine() {
val text = "abc"
val fontSize = 20.0f
val layoutWidth = (text.length - 1) * fontSize
val lineHeight = 1.5f
val paragraph = simpleParagraph(
text = text,
fontSize = fontSize,
lineHeight = lineHeight
)
paragraph.layout(ParagraphConstraints(width = layoutWidth))
val paragraphImpl = paragraph.paragraphImpl
val lastLine = paragraphImpl.lineCount - 1
// In the sample_font.ttf, the height of the line should be
// fontSize + 0.2 * fontSize(line gap)
assertThat(paragraphImpl.getLineHeight(lastLine), equalTo(1.2f * fontSize))
}
@Test(expected = IllegalArgumentException::class)
fun lineHeight_whenNegative_throwsIAE() {
Paragraph(
text = StringBuilder(""),
textStyles = listOf(),
paragraphStyle = ParagraphStyle(
lineHeight = -1.0f
)
)
}
@Test
fun textStyle_setFontSizeOnWholeText() {
val text = "abcde"
val fontSize = 20.0f
val textStyle = TextStyle(fontSize = fontSize)
val paragraphWidth = fontSize * text.length
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
)
paragraph.layout(ParagraphConstraints(width = paragraphWidth))
val paragraphImpl = paragraph.paragraphImpl
// Make sure there is only one line, so that we can use getLineRight to test fontSize.
assertThat(paragraphImpl.lineCount, equalTo(1))
// Notice that in this test font, the width of character equals to fontSize.
assertThat(paragraphImpl.getLineWidth(0), equalTo(fontSize * text.length))
}
@Test
fun textStyle_setFontSizeOnPartOfText() {
val text = "abcde"
val fontSize = 20.0f
val textStyleFontSize = 30.0f
val textStyle = TextStyle(fontSize = textStyleFontSize)
val paragraphWidth = textStyleFontSize * text.length
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length)),
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = paragraphWidth))
val paragraphImpl = paragraph.paragraphImpl
// Make sure there is only one line, so that we can use getLineRight to test fontSize.
assertThat(paragraphImpl.lineCount, equalTo(1))
// Notice that in this test font, the width of character equals to fontSize.
val expectedLineRight = "abc".length * textStyleFontSize + "de".length * fontSize
assertThat(paragraphImpl.getLineWidth(0), equalTo(expectedLineRight))
}
@Test
fun textStyle_seFontSizeTwice_lastOneOverwrite() {
val text = "abcde"
val fontSize = 20.0f
val textStyle = TextStyle(fontSize = fontSize)
val fontSizeOverwrite = 30.0f
val textStyleOverwrite = TextStyle(fontSize = fontSizeOverwrite)
val paragraphWidth = fontSizeOverwrite * text.length
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(
ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length),
ParagraphBuilder.TextStyleIndex(textStyleOverwrite, 0, "abc".length)
)
)
paragraph.layout(ParagraphConstraints(width = paragraphWidth))
val paragraphImpl = paragraph.paragraphImpl
// Make sure there is only one line, so that we can use getLineRight to test fontSize.
assertThat(paragraphImpl.lineCount, equalTo(1))
// Notice that in this test font, the width of character equals to fontSize.
val expectedWidth = "abc".length * fontSizeOverwrite + "de".length * fontSize
assertThat(paragraphImpl.getLineWidth(0), equalTo(expectedWidth))
}
@Test
fun textStyle_fontSizeScale() {
val text = "abcde"
val fontSize = 20f
val fontSizeScale = 0.5f
val textStyle = TextStyle(fontSizeScale = fontSizeScale)
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length)),
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val paragraphImpl = paragraph.paragraphImpl
assertThat(
paragraphImpl.getLineRight(0),
equalTo(text.length * fontSize * fontSizeScale)
)
}
@Test
fun textStyle_fontSizeScaleNested() {
val text = "abcde"
val fontSize = 20f
val fontSizeScale = 0.5f
val textStyle = TextStyle(fontSizeScale = fontSizeScale)
val fontSizeScaleNested = 2f
val textStyleNested = TextStyle(fontSizeScale = fontSizeScaleNested)
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(
ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length),
ParagraphBuilder.TextStyleIndex(textStyleNested, 0, text.length)
),
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val paragraphImpl = paragraph.paragraphImpl
assertThat(
paragraphImpl.getLineRight(0),
equalTo(text.length * fontSize * fontSizeScale * fontSizeScaleNested)
)
}
@Test
fun textStyle_fontSizeScaleWithFontSizeFirst() {
val text = "abcde"
val paragraphFontSize = 20f
val fontSize = 30f
val fontSizeStyle = TextStyle(fontSize = fontSize)
val fontSizeScale = 0.5f
val fontSizeScaleStyle = TextStyle(fontSizeScale = fontSizeScale)
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(
ParagraphBuilder.TextStyleIndex(fontSizeStyle, 0, text.length),
ParagraphBuilder.TextStyleIndex(fontSizeScaleStyle, 0, text.length)
),
fontSize = paragraphFontSize
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val paragraphImpl = paragraph.paragraphImpl
assertThat(
paragraphImpl.getLineRight(0),
equalTo(text.length * fontSize * fontSizeScale)
)
}
@Test
fun textStyle_fontSizeScaleWithFontSizeSecond() {
val text = "abcde"
val paragraphFontSize = 20f
val fontSize = 30f
val fontSizeStyle = TextStyle(fontSize = fontSize)
val fontSizeScale = 0.5f
val fontSizeScaleStyle = TextStyle(fontSizeScale = fontSizeScale)
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(
ParagraphBuilder.TextStyleIndex(fontSizeScaleStyle, 0, text.length),
ParagraphBuilder.TextStyleIndex(fontSizeStyle, 0, text.length)
),
fontSize = paragraphFontSize
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val paragraphImpl = paragraph.paragraphImpl
assertThat(
paragraphImpl.getLineRight(0),
equalTo(text.length * fontSize)
)
}
@Test
fun textStyle_fontSizeScaleWithFontSizeNested() {
val text = "abcde"
val paragraphFontSize = 20f
val fontSize = 30f
val fontSizeStyle = TextStyle(fontSize = fontSize)
val fontSizeScale1 = 0.5f
val fontSizeScaleStyle1 = TextStyle(fontSizeScale = fontSizeScale1)
val fontSizeScale2 = 2f
val fontSizeScaleStyle2 = TextStyle(fontSizeScale = fontSizeScale2)
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(
ParagraphBuilder.TextStyleIndex(fontSizeScaleStyle1, 0, text.length),
ParagraphBuilder.TextStyleIndex(fontSizeStyle, 0, text.length),
ParagraphBuilder.TextStyleIndex(fontSizeScaleStyle2, 0, text.length)
),
fontSize = paragraphFontSize
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val paragraphImpl = paragraph.paragraphImpl
assertThat(
paragraphImpl.getLineRight(0),
equalTo(text.length * fontSize * fontSizeScale2)
)
}
@Test
fun textStyle_setLetterSpacingOnWholeText() {
val text = "abcde"
val fontSize = 20.0f
val letterSpacing = 5.0f
val textStyle = TextStyle(letterSpacing = letterSpacing)
val paragraphWidth = fontSize * (1 + letterSpacing) * text.length
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length)),
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = paragraphWidth))
val paragraphImpl = paragraph.paragraphImpl
// Make sure there is only one line, so that we can use getLineRight to test fontSize.
assertThat(paragraphImpl.lineCount, equalTo(1))
// Notice that in this test font, the width of character equals to fontSize.
assertThat(
paragraphImpl.getLineWidth(0),
equalTo(fontSize * text.length * (1 + letterSpacing))
)
}
@Test
fun textStyle_setLetterSpacingOnPartText() {
val text = "abcde"
val fontSize = 20.0f
val letterSpacing = 5.0f
val textStyle = TextStyle(letterSpacing = letterSpacing)
val paragraphWidth = fontSize * (1 + letterSpacing) * text.length
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length)),
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = paragraphWidth))
val paragraphImpl = paragraph.paragraphImpl
// Make sure there is only one line, so that we can use getLineRight to test fontSize.
assertThat(paragraphImpl.lineCount, equalTo(1))
// Notice that in this test font, the width of character equals to fontSize.
val expectedWidth = ("abc".length * letterSpacing + text.length) * fontSize
assertThat(paragraphImpl.getLineWidth(0), equalTo(expectedWidth))
}
@Test
fun textStyle_setLetterSpacingTwice_lastOneOverwrite() {
val text = "abcde"
val fontSize = 20.0f
val letterSpacing = 5.0f
val textStyle = TextStyle(letterSpacing = letterSpacing)
val letterSpacingOverwrite = 10.0f
val textStyleOverwrite = TextStyle(letterSpacing = letterSpacingOverwrite)
val paragraphWidth = fontSize * (1 + letterSpacingOverwrite) * text.length
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(
ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length),
ParagraphBuilder.TextStyleIndex(textStyleOverwrite, 0, "abc".length)
),
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = paragraphWidth))
val paragraphImpl = paragraph.paragraphImpl
// Make sure there is only one line, so that we can use getLineRight to test fontSize.
assertThat(paragraphImpl.lineCount, equalTo(1))
// Notice that in this test font, the width of character equals to fontSize.
val expectedWidth = "abc".length * (1 + letterSpacingOverwrite) * fontSize +
"de".length * (1 + letterSpacing) * fontSize
assertThat(paragraphImpl.getLineWidth(0), equalTo(expectedWidth))
}
@Test
fun textStyle_setWordSpacingOnWholeText() {
if (!BuildCompat.isAtLeastQ()) return
val text = "ab cd"
val fontSize = 20.0f
val wordSpacing = 5.0f
val textStyle = TextStyle(wordSpacing = wordSpacing)
val paragraphWidth = fontSize * (1 + text.length)
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length)),
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = paragraphWidth))
val paragraphImpl = paragraph.paragraphImpl
// Make sure there is only one line, so that we can use getLineWidth to test fontSize.
assertThat(paragraphImpl.lineCount, equalTo(1))
// Notice that in this test font, the width of character equals to fontSize.
assertThat(
paragraphImpl.getLineWidth(0),
equalTo(fontSize * text.length + wordSpacing)
)
}
@Test
fun textStyle_setWordSpacingOnPartText() {
if (!BuildCompat.isAtLeastQ()) return
val text = "a b c"
val fontSize = 20.0f
val wordSpacing = 5.0f
val textStyle = TextStyle(wordSpacing = wordSpacing)
val paragraphWidth = fontSize * (1 + text.length)
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "a b".length)),
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = paragraphWidth))
val paragraphImpl = paragraph.paragraphImpl
// Make sure there is only one line, so that we can use getLineWidth to test fontSize.
assertThat(paragraphImpl.lineCount, equalTo(1))
// Notice that in this test font, the width of character equals to fontSize.
assertThat(
paragraphImpl.getLineWidth(0),
equalTo(fontSize * text.length + wordSpacing)
)
}
@Test
fun textStyle_setWordSpacingTwice_lastOneOverwrite() {
if (!BuildCompat.isAtLeastQ()) return
val text = "a b c"
val fontSize = 20.0f
val wordSpacing = 2.0f
val textStyle = TextStyle(wordSpacing = wordSpacing)
val wordSpacingOverwrite = 5.0f
val textStyleOverwrite = TextStyle(wordSpacing = wordSpacingOverwrite)
val paragraphWidth = fontSize * (1 + text.length)
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(
ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length),
ParagraphBuilder.TextStyleIndex(textStyleOverwrite, 0, "a b".length)
),
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = paragraphWidth))
val paragraphImpl = paragraph.paragraphImpl
// Make sure there is only one line, so that we can use getLineWidth to test fontSize.
assertThat(paragraphImpl.lineCount, equalTo(1))
// Notice that in this test font, the width of character equals to fontSize.
assertThat(
paragraphImpl.getLineWidth(0),
equalTo(fontSize * text.length + wordSpacing + wordSpacingOverwrite)
)
}
@Test
fun textStyle_textIndent_onSingleLine() {
val text = "abc"
val fontSize = 20.0f
val indent = 20.0f
val textStyle = TextStyle(textIndent = TextIndent(firstLine = indent.px))
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(
ParagraphBuilder.TextStyleIndex(
textStyle = textStyle,
start = 0,
end = text.length
)
),
fontSize = fontSize,
fontFamily = fontFamilyMeasureFont
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val paragraphImpl = paragraph.paragraphImpl
// This offset should point to the first character 'a' if indent is applied.
// Otherwise this offset will point to the second character 'b'.
val offset = Offset(indent + 1, fontSize / 2)
// The position corresponding to the offset should be the first char 'a'.
assertThat(paragraphImpl.getPositionForOffset(offset).offset, equalTo(0))
}
@Test
fun textStyle_textIndent_onFirstLine() {
val text = "abcdef"
val fontSize = 20.0f
val indent = 20.0f
val textStyle = TextStyle(textIndent = TextIndent(firstLine = indent.px))
val paragraphWidth = "abcd".length * fontSize
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(
ParagraphBuilder.TextStyleIndex(
textStyle = textStyle,
start = 0,
end = text.length
)
),
fontSize = fontSize,
fontFamily = fontFamilyMeasureFont
)
paragraph.layout(ParagraphConstraints(width = paragraphWidth))
val paragraphImpl = paragraph.paragraphImpl
assertThat(paragraphImpl.lineCount, equalTo(2))
// This offset should point to the first character of the first line if indent is applied.
// Otherwise this offset will point to the second character of the second line.
val offset = Offset(indent + 1, fontSize / 2)
// The position corresponding to the offset should be the first char 'a'.
assertThat(paragraphImpl.getPositionForOffset(offset).offset, equalTo(0))
}
@Test
fun textStyle_textIndent_onRestLine() {
val text = "abcde"
val fontSize = 20.0f
val indent = 20.0f
val textStyle = TextStyle(
textIndent = TextIndent(
firstLine = 0.px,
restLine = indent.px
)
)
val paragraphWidth = "abc".length * fontSize
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(
ParagraphBuilder.TextStyleIndex(
textStyle = textStyle,
start = 0,
end = text.length
)
),
fontSize = fontSize,
fontFamily = fontFamilyMeasureFont
)
paragraph.layout(ParagraphConstraints(width = paragraphWidth))
val paragraphImpl = paragraph.paragraphImpl
// This offset should point to the first character of the second line if indent is applied.
// Otherwise this offset will point to the second character of the second line.
val offset = Offset(indent + 1, fontSize / 2 + fontSize)
// The position corresponding to the offset should be the 'd' in the second line.
assertThat(
paragraphImpl.getPositionForOffset(offset).offset,
equalTo("abcd".length - 1)
)
}
@Test
fun textStyle_textAlign_whenAlignLeft_returnsZeroForGetLineLeft() {
val texts = listOf("aa", "\u05D0\u05D0")
val fontSize = 20.0f
texts.map { text ->
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(
ParagraphBuilder.TextStyleIndex(
textStyle = TextStyle(textAlign = TextAlign.Left),
start = 0,
end = text.length
)
),
fontSize = fontSize
)
val layoutWidth = (text.length + 2) * fontSize
paragraph.layout(ParagraphConstraints(width = layoutWidth))
val paragraphImpl = paragraph.paragraphImpl
assertThat(paragraphImpl.getLineLeft(0), equalTo(0.0f))
}
}
@Test
fun textStyle_textAlign_whenAlignRight_returnsLayoutWidthForGetLineRight() {
val texts = listOf("aa", "\u05D0\u05D0")
val fontSize = 20.0f
texts.map { text ->
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(
ParagraphBuilder.TextStyleIndex(
textStyle = TextStyle(textAlign = TextAlign.Right),
start = 0,
end = text.length
)
),
fontSize = fontSize
)
val layoutWidth = (text.length + 2) * fontSize
paragraph.layout(ParagraphConstraints(width = layoutWidth))
val paragraphImpl = paragraph.paragraphImpl
assertThat(paragraphImpl.getLineRight(0), equalTo(layoutWidth))
}
}
@Test
fun textStyle_textAlign_whenAlignCenter_textIsCentered() {
val texts = listOf("aa", "\u05D0\u05D0")
val fontSize = 20.0f
texts.map { text ->
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(
ParagraphBuilder.TextStyleIndex(
textStyle = TextStyle(textAlign = TextAlign.Center),
start = 0,
end = text.length
)
),
fontSize = fontSize
)
val layoutWidth = (text.length + 2) * fontSize
paragraph.layout(ParagraphConstraints(width = layoutWidth))
val textWidth = text.length * fontSize
val paragraphImpl = paragraph.paragraphImpl
assertThat(paragraphImpl.getLineLeft(0),
equalTo(layoutWidth / 2 - textWidth / 2))
assertThat(paragraphImpl.getLineRight(0),
equalTo(layoutWidth / 2 + textWidth / 2))
}
}
@Test
fun textStyle_textAlign_whenAlignStart_withLTR_returnsZeroForGetLineLeft() {
val text = "aa"
val fontSize = 20.0f
val layoutWidth = (text.length + 2) * fontSize
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(
ParagraphBuilder.TextStyleIndex(
textStyle = TextStyle(textAlign = TextAlign.Start),
start = 0,
end = text.length
)
),
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = layoutWidth))
val paragraphImpl = paragraph.paragraphImpl
assertThat(paragraphImpl.getLineLeft(0), equalTo(0.0f))
}
@Test
fun textStyle_textAlign_whenAlignEnd_withLTR_returnsLayoutWidthForGetLineRight() {
val text = "aa"
val fontSize = 20.0f
val layoutWidth = (text.length + 2) * fontSize
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(
ParagraphBuilder.TextStyleIndex(
textStyle = TextStyle(textAlign = TextAlign.End),
start = 0,
end = text.length
)
),
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = layoutWidth))
val paragraphImpl = paragraph.paragraphImpl
assertThat(paragraphImpl.getLineRight(0), equalTo(layoutWidth))
}
@Test
fun textStyle_textAlign_whenAlignStart_withRTL_returnsLayoutWidthForGetLineRight() {
val text = "\u05D0\u05D0"
val fontSize = 20.0f
val layoutWidth = (text.length + 2) * fontSize
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(
ParagraphBuilder.TextStyleIndex(
textStyle = TextStyle(textAlign = TextAlign.Start),
start = 0,
end = text.length
)
),
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = layoutWidth))
val paragraphImpl = paragraph.paragraphImpl
assertThat(paragraphImpl.getLineRight(0), equalTo(layoutWidth))
}
@Test
fun textStyle_textAlign_whenAlignEnd_withRTL_returnsZeroForGetLineLeft() {
val text = "\u05D0\u05D0"
val fontSize = 20.0f
val layoutWidth = (text.length + 2) * fontSize
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(
ParagraphBuilder.TextStyleIndex(
textStyle = TextStyle(textAlign = TextAlign.End),
start = 0,
end = text.length
)
),
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = layoutWidth))
val paragraphImpl = paragraph.paragraphImpl
assertThat(paragraphImpl.getLineLeft(0), equalTo(0.0f))
}
@Test
fun textStyle_fontFamily_changesMeasurement() {
val text = "ad"
val fontSize = 20.0f
// custom 100 regular font has b as the wide glyph
// custom 200 regular font has d as the wide glyph
val textStyle = TextStyle(fontFamily = fontFamilyCustom200)
// a is rendered in paragraphStyle font (custom 100), it will not have wide glyph
// d is rendered in textStyle font (custom 200), and it will be wide glyph
val expectedWidth = fontSize + fontSize * 3
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(
ParagraphBuilder.TextStyleIndex(textStyle, "a".length, text.length)
),
fontSize = fontSize,
fontFamily = fontFamilyCustom100
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val paragraphImpl = paragraph.paragraphImpl
assertThat(paragraphImpl.lineCount, equalTo(1))
assertThat(paragraphImpl.getLineWidth(0), equalTo(expectedWidth))
}
@Test
fun textStyle_fontFeature_turnOffKern() {
val text = "AaAa"
val fontSize = 20.0f
// This fontFeatureSetting turns off the kerning
val textStyle = TextStyle(fontFeatureSettings = "\"kern\" 0")
val paragraph = simpleParagraph(
text = text,
textStyles = listOf(
ParagraphBuilder.TextStyleIndex(textStyle, 0, "aA".length)
),
fontSize = fontSize,
fontFamily = fontFamilyKernFont
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val paragraphImpl = paragraph.paragraphImpl
// Two characters are kerning, so minus 0.4 * fontSize
val expectedWidth = text.length * fontSize - 0.4f * fontSize
assertThat(paragraphImpl.lineCount, equalTo(1))
assertThat(paragraphImpl.getLineWidth(0), equalTo(expectedWidth))
}
@Test
fun testStyle_shadow() {
val text = "abcde"
val fontSize = 20f
val paragraphWidth = fontSize * text.length
val textStyle = TextStyle(shadow = Shadow(Color(0xFF00FF00.toInt()), Offset(1f, 2f), 3.px))
val paragraphShadow = simpleParagraph(
text = text,
textStyles = listOf(
ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length)
)
)
paragraphShadow.layout(ParagraphConstraints(width = paragraphWidth))
val paragraph = simpleParagraph(text = text)
paragraph.layout(ParagraphConstraints(width = paragraphWidth))
assertThat(paragraphShadow.bitmap(), not(equalToBitmap(paragraph.bitmap())))
}
@Test
fun testGetPathForRange_singleLine() {
// Setup test.
val text = "abc"
val fontSize = 20f
val paragraph = simpleParagraph(
text = text,
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val paragraphImpl = paragraph.paragraphImpl
val expectedPath = Path()
val lineLeft = paragraphImpl.getLineLeft(0)
val lineRight = paragraphImpl.getLineRight(0)
expectedPath.addRect(Rect(lineLeft, 0f, lineRight - fontSize, fontSize))
// Run.
// Select "ab"
val actualPath = paragraph.getPathForRange(0, 2)
// Assert.
val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
assertThat(diff, equalTo(Rect.zero))
}
@Test
fun testGetPathForRange_multiLines() {
// Setup test.
val text = "abc\nabc"
val fontSize = 20f
val paragraph = simpleParagraph(
text = text,
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val paragraphImpl = paragraph.paragraphImpl
val expectedPath = Path()
val firstLineLeft = paragraphImpl.getLineLeft(0)
val secondLineLeft = paragraphImpl.getLineLeft(1)
val firstLineRight = paragraphImpl.getLineRight(0)
val secondLineRight = paragraphImpl.getLineRight(1)
expectedPath.addRect(Rect(firstLineLeft + fontSize, 0f, firstLineRight, fontSize))
expectedPath.addRect(Rect(
secondLineLeft,
fontSize,
secondLineRight - fontSize,
paragraph.height))
// Run.
// Select "bc\nab"
val actualPath = paragraph.getPathForRange(1, 6)
// Assert.
val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
assertThat(diff, equalTo(Rect.zero))
}
@Test
fun testGetPathForRange_Bidi() {
// Setup test.
val textLTR = "Hello"
val textRTL = "שלום"
val text = textLTR + textRTL
val selectionLTRStart = 2
val selectionRTLEnd = 2
val fontSize = 20f
val paragraph = simpleParagraph(
text = text,
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val paragraphImpl = paragraph.paragraphImpl
val expectedPath = Path()
val lineLeft = paragraphImpl.getLineLeft(0)
val lineRight = paragraphImpl.getLineRight(0)
expectedPath.addRect(
Rect(
lineLeft + selectionLTRStart * fontSize,
0f,
lineLeft + textLTR.length * fontSize,
fontSize))
expectedPath.addRect(Rect(lineRight - selectionRTLEnd * fontSize, 0f, lineRight, fontSize))
// Run.
// Select "llo..של"
val actualPath =
paragraph.getPathForRange(selectionLTRStart, textLTR.length + selectionRTLEnd)
// Assert.
val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
assertThat(diff, equalTo(Rect.zero))
}
@Test
fun testGetPathForRange_Start_Equals_End_Returns_Empty_Path() {
val text = "abc"
val fontSize = 20f
val paragraph = simpleParagraph(
text = text,
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val actualPath = paragraph.getPathForRange(1, 1)
assertThat(actualPath.getBounds(), equalTo(Rect.zero))
}
@Test
fun testGetPathForRange_Empty_Text() {
val text = ""
val fontSize = 20f
val paragraph = simpleParagraph(
text = text,
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val actualPath = paragraph.getPathForRange(0, 0)
assertThat(actualPath.getBounds(), equalTo(Rect.zero))
}
@Test
fun testGetPathForRange_Surrogate_Pair_Start_Middle_Second_Character_Selected() {
// Setup test.
val text = "\uD834\uDD1E\uD834\uDD1F"
val fontSize = 20f
val paragraph = simpleParagraph(
text = text,
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val paragraphImpl = paragraph.paragraphImpl
val expectedPath = Path()
val lineRight = paragraphImpl.getLineRight(0)
expectedPath.addRect(Rect(lineRight / 2, 0f, lineRight, fontSize))
// Run.
// Try to select "\uDD1E\uD834\uDD1F", only "\uD834\uDD1F" is selected.
val actualPath = paragraph.getPathForRange(1, text.length)
// Assert.
val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
assertThat(diff, equalTo(Rect.zero))
}
@Test
fun testGetPathForRange_Surrogate_Pair_End_Middle_Second_Character_Selected() {
// Setup test.
val text = "\uD834\uDD1E\uD834\uDD1F"
val fontSize = 20f
val paragraph = simpleParagraph(
text = text,
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val paragraphImpl = paragraph.paragraphImpl
val expectedPath = Path()
val lineRight = paragraphImpl.getLineRight(0)
expectedPath.addRect(Rect(lineRight / 2, 0f, lineRight, fontSize))
// Run.
// Try to select "\uDD1E\uD834", actually "\uD834\uDD1F" is selected.
val actualPath = paragraph.getPathForRange(1, text.length - 1)
// Assert.
val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
assertThat(diff, equalTo(Rect.zero))
}
@Test
fun testGetPathForRange_Surrogate_Pair_Start_Middle_End_Same_Character_Returns_Line_Segment() {
// Setup test.
val text = "\uD834\uDD1E\uD834\uDD1F"
val fontSize = 20f
val paragraph = simpleParagraph(
text = text,
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val paragraphImpl = paragraph.paragraphImpl
val expectedPath = Path()
val lineRight = paragraphImpl.getLineRight(0)
expectedPath.addRect(Rect(lineRight / 2, 0f, lineRight / 2, fontSize))
// Run.
// Try to select "\uDD1E", get vertical line segment after this character.
val actualPath = paragraph.getPathForRange(1, 2)
// Assert.
val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
assertThat(diff, equalTo(Rect.zero))
}
@Test
fun testGetPathForRange_Emoji_Sequence() {
// Setup test.
val text = "\u1F600\u1F603\u1F604\u1F606"
val fontSize = 20f
val paragraph = simpleParagraph(
text = text,
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val paragraphImpl = paragraph.paragraphImpl
val expectedPath = Path()
val lineLeft = paragraphImpl.getLineLeft(0)
val lineRight = paragraphImpl.getLineRight(0)
expectedPath.addRect(Rect(lineLeft + fontSize, 0f, lineRight - fontSize, fontSize))
// Run.
// Select "\u1F603\u1F604"
val actualPath = paragraph.getPathForRange(1, text.length - 1)
// Assert.
val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
assertThat(diff, equalTo(Rect.zero))
}
@Test
fun testGetPathForRange_Unicode_200D_Return_Line_Segment() {
// Setup test.
val text = "\u200D"
val fontSize = 20f
val paragraph = simpleParagraph(
text = text,
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val paragraphImpl = paragraph.paragraphImpl
val expectedPath = Path()
val lineLeft = paragraphImpl.getLineLeft(0)
val lineRight = paragraphImpl.getLineRight(0)
expectedPath.addRect(Rect(lineLeft, 0f, lineRight, fontSize))
// Run.
val actualPath = paragraph.getPathForRange(0, 1)
// Assert.
assertThat(lineLeft, equalTo(lineRight))
val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
assertThat(diff, equalTo(Rect.zero))
}
@Test
fun testGetPathForRange_Unicode_2066_Return_Line_Segment() {
// Setup tests.
val text = "\u2066"
val fontSize = 20f
val paragraph = simpleParagraph(
text = text,
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val paragraphImpl = paragraph.paragraphImpl
val expectedPath = Path()
val lineLeft = paragraphImpl.getLineLeft(0)
val lineRight = paragraphImpl.getLineRight(0)
expectedPath.addRect(Rect(lineLeft, 0f, lineRight, fontSize))
// Run.
val actualPath = paragraph.getPathForRange(0, 1)
// Assert.
assertThat(lineLeft, equalTo(lineRight))
val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
assertThat(diff, equalTo(Rect.zero))
}
@Test
fun testGetWordBoundary() {
val text = "abc def"
val fontSize = 20f
val paragraph = simpleParagraph(
text = text,
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val result = paragraph.getWordBoundary(text.indexOf('a'))
assertThat(result.start, equalTo(text.indexOf('a')))
assertThat(result.end, equalTo(text.indexOf(' ')))
}
@Test
fun testGetWordBoundary_Bidi() {
val text = "abc \u05d0\u05d1\u05d2 def"
val fontSize = 20f
val paragraph = simpleParagraph(
text = text,
fontFamily = fontFamilyMeasureFont,
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
val resultEnglish = paragraph.getWordBoundary(text.indexOf('a'))
val resultHebrew = paragraph.getWordBoundary(text.indexOf('\u05d1'))
assertThat(resultEnglish.start, equalTo(text.indexOf('a')))
assertThat(resultEnglish.end, equalTo(text.indexOf(' ')))
assertThat(resultHebrew.start, equalTo(text.indexOf('\u05d0')))
assertThat(resultHebrew.end, equalTo(text.indexOf('\u05d2') + 1))
}
private fun simpleParagraph(
text: CharSequence = "",
textAlign: TextAlign? = null,
textDirection: TextDirection? = null,
fontSize: Float? = null,
maxLines: Int? = null,
lineHeight: Float? = null,
textStyles: List<ParagraphBuilder.TextStyleIndex> = listOf(),
fontFamily: FontFamily = fontFamilyMeasureFont
): Paragraph {
return Paragraph(
text = StringBuilder(text),
textStyles = textStyles,
paragraphStyle = ParagraphStyle(
textAlign = textAlign,
textDirection = textDirection,
maxLines = maxLines,
fontFamily = fontFamily,
fontSize = fontSize,
lineHeight = lineHeight
)
)
}
}