All Downloads are FREE. Search and download functionalities are using the official Maven repository.

desktopTest.androidx.compose.ui.text.DesktopParagraphTest.kt Maven / Gradle / Ivy

/*
 * Copyright 2020 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.compose.ui.text

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.text.font.createFontFamilyResolver
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.platform.Font
import androidx.compose.ui.text.style.BaselineShift
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.text.style.TextIndent
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.sp
import com.google.common.truth.Truth
import org.junit.Assume.assumeTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import kotlin.math.roundToInt

@RunWith(JUnit4::class)
class DesktopParagraphTest {
    @get:Rule
    val rule = createComposeRule()

    private val fontFamilyResolver = createFontFamilyResolver()
    private val defaultDensity = Density(density = 1f)
    private val fontFamilyMeasureFont =
        FontFamily(
            Font(
                "font/sample_font.ttf",
                weight = FontWeight.Normal,
                style = FontStyle.Normal
            )
        )

    @Test
    fun getBoundingBox_basic() {
        with(defaultDensity) {
            val text = "abc"
            val fontSize = 50.sp
            val fontSizeInPx = fontSize.toPx()
            val paragraph = simpleParagraph(
                text = text,
                style = TextStyle(fontSize = fontSize)
            )

            for (i in 0..text.length - 1) {
                val box = paragraph.getBoundingBox(i)
                Truth.assertThat(box.left).isEqualTo(i * fontSizeInPx)
                Truth.assertThat(box.right).isEqualTo((i + 1) * fontSizeInPx)
                Truth.assertThat(box.top).isZero()
                Truth.assertThat(box.bottom).isEqualTo(fontSizeInPx)
            }
        }
    }

    @Test
    fun `test cursor position of LTR text in LTR and RTL paragraphs`() {
        // LTR paragraph
        with(defaultDensity) {
            val text = "abc"
            val fontSize = 50.sp
            val fontSizeInPx = fontSize.toPx()
            val paragraph = simpleParagraph(
                text = text,
                style = TextStyle(fontSize = fontSize, textDirection = TextDirection.Ltr)
            )

            repeat(4) {
                Truth.assertThat(paragraph.getCursorRect(it).left.roundToInt())
                    .isEqualTo((fontSizeInPx * it).roundToInt())
                Truth.assertThat(paragraph.getCursorRect(it).left.roundToInt())
                    .isEqualTo(paragraph.getCursorRect(it).right.roundToInt())
            }
        }

        // RTL paragraph
        with(defaultDensity) {
            val text = "abc"
            val fontSize = 50.sp
            val fontSizeInPx = fontSize.toPx()
            val widthInPx = fontSizeInPx * 10
            val paragraph = simpleParagraph(
                text = text,
                width = widthInPx,
                style = TextStyle(fontSize = fontSize, textDirection = TextDirection.Rtl)
            )

            val leftX = paragraph.getLineLeft(0)
            Truth.assertThat(leftX.roundToInt()).isEqualTo((widthInPx - 3 * fontSizeInPx).roundToInt())
            repeat(4) {
                Truth.assertThat(paragraph.getCursorRect(it).left.roundToInt())
                    .isEqualTo((leftX + fontSizeInPx * it).roundToInt())
                Truth.assertThat(paragraph.getCursorRect(it).left.roundToInt())
                    .isEqualTo(paragraph.getCursorRect(it).right.roundToInt())
            }
        }
    }

    @Test
    fun `test cursor position of RTL text in LTR and RTL paragraphs`() {
        // LTR paragraph
        with(defaultDensity) {
            val text = "אסד"
            val fontSize = 50.sp
            val fontSizeInPx = fontSize.toPx()
            val paragraph = simpleParagraph(
                text = text,
                style = TextStyle(fontSize = fontSize, textDirection = TextDirection.Ltr)
            )

            repeat(4) {
                Truth.assertThat(paragraph.getCursorRect(it).left.roundToInt())
                    .isEqualTo(((3 - it) * fontSizeInPx).roundToInt())
            }
        }

        // RTL paragraph
        with(defaultDensity) {
            val text = "אסד"
            val fontSize = 50.sp
            val fontSizeInPx = fontSize.toPx()
            val widthInPx = fontSizeInPx * 10
            val paragraph = simpleParagraph(
                text = text,
                width = widthInPx,
                style = TextStyle(fontSize = fontSize, textDirection = TextDirection.Rtl)
            )

            val leftX = paragraph.getLineLeft(0)
            Truth.assertThat(leftX.roundToInt()).isEqualTo((widthInPx - 3 * fontSizeInPx).roundToInt())
            repeat(4) {
                Truth.assertThat(paragraph.getCursorRect(it).left.roundToInt())
                    .isEqualTo((leftX + (3 - it) * fontSizeInPx).roundToInt())
            }
        }
    }

    @Test
    fun `test cursor position of BiDi text in LTR and RTL paragraphs`() {
        // LTR paragraph
        with(defaultDensity) {
            val text = "asd אסד"
            val fontSize = 50.sp
            val fontSizeInPx = fontSize.toPx()
            val paragraph = simpleParagraph(
                text = text,
                style = TextStyle(fontSize = fontSize, textDirection = TextDirection.Ltr)
            )

            val rightX = paragraph.getLineRight(0)
            (0..3).forEach {
                Truth.assertThat(paragraph.getCursorRect(it).left.roundToInt())
                    .isEqualTo((it * fontSizeInPx).roundToInt())
            }
            (4..7).forEach {
                Truth.assertThat(paragraph.getCursorRect(it).left.roundToInt())
                    .isEqualTo((rightX - (it - 4) * fontSizeInPx).roundToInt())
            }
        }
        with(defaultDensity) {
            val text = "אסד asd"
            val fontSize = 50.sp
            val fontSizeInPx = fontSize.toPx()
            val paragraph = simpleParagraph(
                text = text,
                style = TextStyle(fontSize = fontSize, textDirection = TextDirection.Ltr)
            )

            val leftX = paragraph.getLineLeft(0)
            (0..3).forEach {
                Truth.assertThat(paragraph.getCursorRect(it).left.roundToInt())
                    .isEqualTo((leftX + (3 - it) * fontSizeInPx).roundToInt())
            }
            (7 downTo 4).forEach {
                Truth.assertThat(paragraph.getCursorRect(it).left.roundToInt())
                    .isEqualTo((it * fontSizeInPx).roundToInt())
            }
        }

        // RTL paragraph
        with(defaultDensity) {
            val text = "asd אסד"
            val fontSize = 50.sp
            val fontSizeInPx = fontSize.toPx()
            val paragraph = simpleParagraph(
                text = text,
                width = 10 * fontSizeInPx,
                style = TextStyle(fontSize = fontSize, textDirection = TextDirection.Rtl)
            )

            val rightX = paragraph.getLineRight(0)
            (3 downTo 0).forEach {
                Truth.assertThat(paragraph.getCursorRect(it).left.roundToInt())
                    .isEqualTo((rightX - (3 - it) * fontSizeInPx).roundToInt())
            }
            (4..7).forEach {
                Truth.assertThat(paragraph.getCursorRect(it).left.roundToInt())
                    .isEqualTo((rightX -  it * fontSizeInPx).roundToInt())
            }
        }
        with(defaultDensity) {
            val text = "אסד asd"
            val fontSize = 50.sp
            val fontSizeInPx = fontSize.toPx()
            val paragraph = simpleParagraph(
                text = text,
                width = 10 * fontSizeInPx,
                style = TextStyle(fontSize = fontSize, textDirection = TextDirection.Rtl)
            )

            val leftX = paragraph.getLineLeft(0)
            val rightX = paragraph.getLineRight(0)
            (0..3).forEach {
                Truth.assertThat(paragraph.getCursorRect(it).left.roundToInt())
                    .isEqualTo((rightX - it * fontSizeInPx).roundToInt())
            }
            (4..7).forEach {
                Truth.assertThat(paragraph.getCursorRect(it).left.roundToInt())
                    .isEqualTo((leftX + (it - 4) * fontSizeInPx).roundToInt())
            }
        }
    }

    @Test
    fun `test cursor position in RTl text when clicking on an empty line`() {
        // Tests if (leftX == rightX) in getOffsetForPosition
        with(defaultDensity) {
            val text = "asd\n"
            val fontSize = 50.sp
            val fontSizeInPx = fontSize.toPx()
            val paragraph = simpleParagraph(
                text = text,
                width = 10 * fontSizeInPx,
                style = TextStyle(fontSize = fontSize, textDirection = TextDirection.Rtl)
            )

            val leftX = paragraph.getLineLeft(0)
            val rightX = paragraph.getLineRight(0)

            val clickX = (leftX + rightX) / 2f
            val secondLineY = (paragraph.getLineBottom(1) + paragraph.getLineTop(1)) / 2f

            Truth.assertThat(paragraph.getOffsetForPosition(Offset(clickX, secondLineY))).isEqualTo(4)
            Truth.assertThat(paragraph.getOffsetForPosition(Offset(leftX - fontSizeInPx, secondLineY))).isEqualTo(4)
            Truth.assertThat(paragraph.getOffsetForPosition(Offset(rightX + fontSizeInPx, secondLineY))).isEqualTo(4)
        }
    }

    @Test
    fun `test getOffsetByPosition`() {
        // LTR
        with(defaultDensity) {
            val text = " abc \ndef ghi"
            val fontSize = 50.sp
            val fontSizeInPx = fontSize.toPx()
            val paragraph = simpleParagraph(
                text = text,
                style = TextStyle(fontSize = fontSize, textDirection = TextDirection.Ltr)
            )

            val firstLineY = (paragraph.getLineBottom(0) + paragraph.getLineTop(0)) / 2f
            (0..5).forEach {
                Truth.assertThat(paragraph.getOffsetForPosition(Offset(x = fontSizeInPx * it, y = firstLineY)))
                    .isEqualTo(it)
            }
            Truth.assertThat(paragraph.getOffsetForPosition(Offset(x = 1000f, y = firstLineY))).isEqualTo(5)
            Truth.assertThat(paragraph.getOffsetForPosition(Offset(x = -100f, y = firstLineY))).isEqualTo(0)

            val secondLineY = (paragraph.getLineBottom(1) + paragraph.getLineTop(1)) / 2f
            Truth.assertThat(paragraph.getOffsetForPosition(Offset(x = 1000f, y = secondLineY))).isEqualTo(13)
            Truth.assertThat(paragraph.getOffsetForPosition(Offset(x = -100f, y = secondLineY))).isEqualTo(6)

            (6..13).forEach {
                Truth.assertThat(paragraph.getOffsetForPosition(Offset(x = fontSizeInPx * (it - 6), y = secondLineY))).isEqualTo(it)
            }
        }

        // RTL
        with(defaultDensity) {
            val text = " אסד \nקשע תטו"
            val fontSize = 50.sp
            val fontSizeInPx = fontSize.toPx()
            val width = 10 * fontSizeInPx
            val paragraph = simpleParagraph(
                text = text,
                width = width,
                style = TextStyle(fontSize = fontSize, textDirection = TextDirection.Rtl)
            )

            val firstLineY = (paragraph.getLineBottom(0) + paragraph.getLineTop(0)) / 2f
            (0..5).forEach {
                Truth.assertThat(paragraph.getOffsetForPosition(Offset(x = width - fontSizeInPx * it, y = firstLineY)))
                    .isEqualTo(it)
            }
            Truth.assertThat(paragraph.getOffsetForPosition(Offset(x = width + fontSizeInPx, y = firstLineY))).isEqualTo(0)
            Truth.assertThat(paragraph.getOffsetForPosition(Offset(x = 0f, y = firstLineY))).isEqualTo(5)

            val secondLineY = 20f + (paragraph.getLineBottom(1) + paragraph.getLineTop(1)) / 2f
            Truth.assertThat(paragraph.getOffsetForPosition(Offset(x = width - 1f, y = secondLineY))).isEqualTo(6)
            Truth.assertThat(paragraph.getOffsetForPosition(Offset(x = -100f, y = secondLineY))).isEqualTo(13)

            (7..13).forEach {
                Truth.assertThat(paragraph.getOffsetForPosition(Offset(x = width - (it - 6) * fontSizeInPx, y = secondLineY))).isEqualTo(it)
            }
        }
    }

    @Test
    fun `test cursor position on line-break`() {
        with(defaultDensity) {
            val text = "abc abc  abc abc abc abc  abc"
            val fontSize = 50.sp
            val fontSizeInPx = fontSize.toPx()
            val width = 8 * fontSizeInPx
            val paragraph = simpleParagraph(
                width = width,
                text = text,
                style = TextStyle(fontSize = fontSize, textDirection = TextDirection.Ltr)
            )

            Truth.assertThat(paragraph.lineCount).isEqualTo(4)
            val y = fontSizeInPx / 2f

            // first line has 2 spaces in the end
            Truth.assertThat(
                paragraph.getOffsetForPosition(Offset(x = width, y = y))
            ).isEqualTo(8)

            // seconds line has 1 space in the end
            Truth.assertThat(
                paragraph.getOffsetForPosition(Offset(x = width, y = y + fontSizeInPx))
            ).isEqualTo(16)

            // 3rd line has 2 spaces in the end
            Truth.assertThat(
                paragraph.getOffsetForPosition(Offset(x = width, y = y + 2 * fontSizeInPx))
            ).isEqualTo(25)

            // 4th line has no spaces
            Truth.assertThat(
                paragraph.getOffsetForPosition(Offset(x = width, y = y + 3 * fontSizeInPx))
            ).isEqualTo(29)
        }
    }

    @Test
    fun `test cursor position in a line with many space in the end`() {
        with(defaultDensity) {
            // 1st: 4 spaces, 2nd: 0 spaces, 3rd: 1 space, 4th: empty line
            val text = "abc    \ndef\ngh \n"
            val fontSize = 50.sp
            val fontSizeInPx = fontSize.toPx()
            val width = 20 * fontSizeInPx
            val paragraph = simpleParagraph(
                width = width,
                text = text,
                style = TextStyle(fontSize = fontSize, textDirection = TextDirection.Ltr)
            )

            val y = fontSizeInPx / 2f
            Truth.assertThat(paragraph.getOffsetForPosition(Offset(x = width, y = y))).isEqualTo(7)
            Truth.assertThat(paragraph.getOffsetForPosition(Offset(x = width, y = y + fontSizeInPx))).isEqualTo(11)
            Truth.assertThat(paragraph.getOffsetForPosition(Offset(x = width, y = y + 2 * fontSizeInPx))).isEqualTo(15)
            Truth.assertThat(paragraph.getOffsetForPosition(Offset(x = width, y = y + 3 * fontSizeInPx))).isEqualTo(16)
        }
    }

    @Test
    fun `test cursor position in a line with many space in the start`() {
        with(defaultDensity) {
            // 1st: 4 spaces, 2nd: 0 spaces, 3rd: 1 space, 4th: empty line
            val text = "    abc\ndef\n gh\n"
            val fontSize = 50.sp
            val fontSizeInPx = fontSize.toPx()
            val width = 20 * fontSizeInPx
            val paragraph = simpleParagraph(
                width = width,
                text = text,
                style = TextStyle(fontSize = fontSize, textDirection = TextDirection.Ltr)
            )

            val y = fontSizeInPx / 2f
            Truth.assertThat(paragraph.getOffsetForPosition(Offset(x = 0f, y = y))).isEqualTo(0)
            Truth.assertThat(paragraph.getOffsetForPosition(Offset(x = 0f, y = y + fontSizeInPx))).isEqualTo(8)
            Truth.assertThat(paragraph.getOffsetForPosition(Offset(x = 0f, y = y + 2 * fontSizeInPx))).isEqualTo(12)
            Truth.assertThat(paragraph.getOffsetForPosition(Offset(x = 0f, y = y + 3 * fontSizeInPx))).isEqualTo(16)
        }
    }

    @Test
    fun getBoundingBox_multicodepoints() {
        assumeTrue(isLinux)
        with(defaultDensity) {
            val text = "h\uD83E\uDDD1\uD83C\uDFFF\u200D\uD83E\uDDB0"
            val fontSize = 50.sp
            val fontSizeInPx = fontSize.toPx()
            val paragraph = simpleParagraph(
                text = text,
                style = TextStyle(fontSize = 50.sp)
            )

            Truth.assertThat(paragraph.getBoundingBox(0))
                .isEqualTo(Rect(0f, 0f, fontSizeInPx, 50f))

            Truth.assertThat(paragraph.getBoundingBox(1))
                .isEqualTo(Rect(fontSizeInPx, 0f, fontSizeInPx * 2.5f, 50f))

            Truth.assertThat(paragraph.getBoundingBox(5))
                .isEqualTo(Rect(fontSizeInPx, 0f, fontSizeInPx * 2.5f, 50f))
        }
    }

    @Test
    fun getLineForOffset() {
        val text = "ab\na"
        val paragraph = simpleParagraph(
            text = text,
            style = TextStyle(fontSize = 50.sp)
        )

        Truth.assertThat(paragraph.getLineForOffset(2))
            .isEqualTo(0)
        Truth.assertThat(paragraph.getLineForOffset(3))
            .isEqualTo(1)
    }

    @Test
    fun getLineEnd() {
        with(defaultDensity) {
            val text = ""
            val paragraph = simpleParagraph(
                text = text,
                style = TextStyle(fontSize = 50.sp)
            )

            Truth.assertThat(paragraph.getLineEnd(0, true))
                .isEqualTo(0)
        }
        with(defaultDensity) {
            val text = "ab\n\nc"
            val paragraph = simpleParagraph(
                text = text,
                style = TextStyle(fontSize = 50.sp)
            )

            Truth.assertThat(paragraph.getLineEnd(0, true))
                .isEqualTo(2)
            Truth.assertThat(paragraph.getLineEnd(1, true))
                .isEqualTo(3)
            Truth.assertThat(paragraph.getLineEnd(2, true))
                .isEqualTo(5)
        }
        with(defaultDensity) {
            val text = "ab\n"
            val paragraph = simpleParagraph(
                text = text,
                style = TextStyle(fontSize = 50.sp)
            )

            Truth.assertThat(paragraph.getLineEnd(0, true))
                .isEqualTo(2)
            Truth.assertThat(paragraph.getLineEnd(1, true))
                .isEqualTo(3)
        }
    }

    @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),
                width = width
            )

            for (i in ltrText.indices) {
                Truth.assertThat(paragraph.getHorizontalPosition(i, true))
                    .isEqualTo(fontSizeInPx * i)
            }

            for (i in 1 until rtlText.length) {
                Truth.assertThat(paragraph.getHorizontalPosition(i + ltrText.length, true))
                    .isEqualTo(width - fontSizeInPx * i)
            }
        }
    }

    @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
                ),
                width = width
            )

            for (i in ltrText.indices) {
                Truth.assertThat(paragraph.getHorizontalPosition(i, false))
                    .isEqualTo(fontSizeInPx * i)
            }

            for (i in rtlText.indices) {
                Truth.assertThat(paragraph.getHorizontalPosition(i + ltrText.length, false))
                    .isEqualTo(width - fontSizeInPx * i)
            }

            Truth.assertThat(paragraph.getHorizontalPosition(text.length, false))
                .isEqualTo(width - rtlText.length * fontSizeInPx)
        }
    }

    @Test
    fun getWordBoundary_spaces() {
        val text = "ab cd  e"
        val paragraph = simpleParagraph(
            text = text,
            style = TextStyle(
                fontFamily = fontFamilyMeasureFont,
                fontSize = 20.sp
            )
        )

        val singleSpaceStartResult = paragraph.getWordBoundary(text.indexOf('b') + 1)
        Truth.assertThat(singleSpaceStartResult.start).isEqualTo(text.indexOf('a'))
        Truth.assertThat(singleSpaceStartResult.end).isEqualTo(text.indexOf('b') + 1)

        val singleSpaceEndResult = paragraph.getWordBoundary(text.indexOf('c'))

        Truth.assertThat(singleSpaceEndResult.start).isEqualTo(text.indexOf('c'))
        Truth.assertThat(singleSpaceEndResult.end).isEqualTo(text.indexOf('d') + 1)

        val doubleSpaceResult = paragraph.getWordBoundary(text.indexOf('d') + 2)
        Truth.assertThat(doubleSpaceResult.start).isEqualTo(text.indexOf('d') + 2)
        Truth.assertThat(doubleSpaceResult.end).isEqualTo(text.indexOf('d') + 2)
    }

    @Test
    fun two_paragraphs_use_common_intrinsics() {
        fun Paragraph.testOffset() = getOffsetForPosition(Offset(0f, 100000f))
        fun Paragraph.paint() = paint(Canvas(ImageBitmap(100, 100)))

        val intrinsics = simpleIntrinsics((1..1000).joinToString(" "))

        val paragraph1 = simpleParagraph(intrinsics, width = 100f)
        val offset1 = paragraph1.testOffset()

        val paragraph2 = simpleParagraph(intrinsics, width = 100000f)
        val offset2 = paragraph2.testOffset()

        Truth.assertThat(paragraph1.testOffset()).isEqualTo(offset1)
        Truth.assertThat(paragraph2.testOffset()).isEqualTo(offset2)

        paragraph2.paint()
        Truth.assertThat(paragraph1.testOffset()).isEqualTo(offset1)
        Truth.assertThat(paragraph2.testOffset()).isEqualTo(offset2)

        paragraph1.paint()
        Truth.assertThat(paragraph1.testOffset()).isEqualTo(offset1)
        Truth.assertThat(paragraph2.testOffset()).isEqualTo(offset2)

        paragraph2.paint()
        Truth.assertThat(paragraph1.testOffset()).isEqualTo(offset1)
        Truth.assertThat(paragraph2.testOffset()).isEqualTo(offset2)
    }

    @Test
    fun `line heights`() {
        val paragraph = simpleParagraph(
            text = "aaa\n\naaa\n\n\naaa\n   \naaa",
            style = TextStyle(fontSize = 50.sp)
        )
        val firstLineHeight = paragraph.getLineHeight(0)

        for (i in 1 until paragraph.lineCount) {
            Truth.assertThat(paragraph.getLineHeight(i)).isEqualTo(firstLineHeight)
        }
    }

    @Test
    fun applies_baseline_shift_to_spans() {
        val helper = buildAnnotatedString {
            append("text")
            withStyle(SpanStyle(baselineShift = BaselineShift.Superscript, fontSize = 16.sp)) {
                append("text")
            }
            withStyle(SpanStyle(baselineShift = BaselineShift.Subscript, fontSize = 16.sp)) {
                append("text")
            }
            append("\ntext")
            withStyle(SpanStyle(baselineShift = BaselineShift.Superscript, fontSize = 16.sp)) {
                append("text")
            }
            withStyle(SpanStyle(baselineShift = BaselineShift.Subscript, fontSize = 16.sp)) {
                append("text")
            }
        }
        val textStyle = TextStyle(
            fontFamily = fontFamilyMeasureFont,
            fontSize = 16.sp
        )
        val paragraph = simpleParagraph(text = helper.text, spanStyles = helper.spanStyles, style = textStyle)
        val paragraphWithoutStyles = simpleParagraph(helper.text, textStyle)

        val firstLineTop = paragraph.getLineTop(0)
        val firstLineBottom = paragraph.getLineBottom(0)
        val secondLineTop = paragraph.getLineTop(1)
        val secondLineBottom = paragraph.getLineBottom(1)

        Truth.assertThat(firstLineBottom - firstLineTop).isEqualTo(29.0f)
        Truth.assertThat(paragraphWithoutStyles.getLineTop(0)).isNotEqualTo(firstLineTop)
        Truth.assertThat(paragraphWithoutStyles.getLineBottom(0)).isNotEqualTo(firstLineBottom)

        Truth.assertThat(secondLineBottom - secondLineTop).isEqualTo(29.0f)
        Truth.assertThat(paragraphWithoutStyles.getLineTop(1)).isNotEqualTo(secondLineTop)
        Truth.assertThat(paragraphWithoutStyles.getLineBottom(1)).isNotEqualTo(secondLineBottom)
    }

    @Test
    fun `applies text indent for paragraph`() {
        fun measureLines(alignment: TextAlign, direction: TextDirection): List {
            val paragraph = simpleParagraph(
                text = "sample\ntext",
                style = TextStyle(
                    fontSize = 20.sp,
                    textIndent = TextIndent(50.sp, 20.sp),
                    textAlign = alignment,
                    textDirection = direction,
                )
            )
            return listOf(
                paragraph.getLineLeft(0).roundToInt(),
                paragraph.getLineRight(0).roundToInt(),
                paragraph.getLineLeft(1).roundToInt(),
                paragraph.getLineRight(1).roundToInt()
            )
        }
        Truth.assertThat(measureLines(TextAlign.Left, TextDirection.Ltr)).isEqualTo(listOf(50, 170, 20, 100))
        Truth.assertThat(measureLines(TextAlign.Center, TextDirection.Ltr)).isEqualTo(listOf(965, 1085, 970, 1050))
        Truth.assertThat(measureLines(TextAlign.Right, TextDirection.Ltr)).isEqualTo(listOf(1830, 1950, 1900, 1980))
        Truth.assertThat(measureLines(TextAlign.Justify, TextDirection.Ltr)).isEqualTo(listOf(50, 170, 20, 100))
        Truth.assertThat(measureLines(TextAlign.Left, TextDirection.Rtl)).isEqualTo(listOf(50, 170, 20, 100))
        Truth.assertThat(measureLines(TextAlign.Center, TextDirection.Rtl)).isEqualTo(listOf(915, 1035, 950, 1030))
        Truth.assertThat(measureLines(TextAlign.Right, TextDirection.Rtl)).isEqualTo(listOf(1830, 1950, 1900, 1980))
        Truth.assertThat(measureLines(TextAlign.Justify, TextDirection.Rtl)).isEqualTo(listOf(1830, 1950, 1900, 1980))
    }

    private fun simpleParagraph(
        text: String = "",
        style: TextStyle? = null,
        maxLines: Int = Int.MAX_VALUE,
        ellipsis: Boolean = false,
        spanStyles: List> = listOf(),
        density: Density? = null,
        width: Float = 2000f
    ): Paragraph {
        return Paragraph(
            text = text,
            spanStyles = spanStyles,
            style = TextStyle(
                fontFamily = fontFamilyMeasureFont
            ).merge(style),
            maxLines = maxLines,
            ellipsis = ellipsis,
            constraints = Constraints(maxWidth = width.ceilToInt()),
            density = density ?: defaultDensity,
            fontFamilyResolver = fontFamilyResolver
        )
    }

    private fun simpleIntrinsics(
        text: String = "",
        style: TextStyle? = null,
        spanStyles: List> = listOf(),
        density: Density? = null
    ): ParagraphIntrinsics {
        return ParagraphIntrinsics(
            text = text,
            spanStyles = spanStyles,
            style = TextStyle(
                fontFamily = fontFamilyMeasureFont
            ).merge(style),
            density = density ?: defaultDensity,
            fontFamilyResolver = fontFamilyResolver
        )
    }

    private fun simpleParagraph(
        intrinsics: ParagraphIntrinsics,
        maxLines: Int = Int.MAX_VALUE,
        ellipsis: Boolean = false,
        width: Float = 2000f
    ): Paragraph {
        return Paragraph(
            paragraphIntrinsics = intrinsics,
            maxLines = maxLines,
            ellipsis = ellipsis,
            constraints = Constraints(maxWidth = width.ceilToInt()),
        )
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy