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

androidAndroidTest.androidx.compose.ui.text.ParagraphIntegrationLineHeightStyleTest.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 android.graphics.Paint.FontMetricsInt
import androidx.compose.ui.text.android.style.lineHeight
import androidx.compose.ui.text.font.toFontFamily
import androidx.compose.ui.text.style.LineHeightStyle
import androidx.compose.ui.text.style.LineHeightStyle.Trim
import androidx.compose.ui.text.style.LineHeightStyle.Alignment
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.sp
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlin.math.abs
import kotlin.math.ceil

@RunWith(AndroidJUnit4::class)
@SmallTest
@OptIn(ExperimentalTextApi::class)
class ParagraphIntegrationLineHeightStyleTest {
    private val fontFamilyMeasureFont = FontTestData.BASIC_MEASURE_FONT.toFontFamily()
    private val context = InstrumentationRegistry.getInstrumentation().context
    private val defaultDensity = Density(density = 1f)
    private val fontSize = 10.sp
    private val lineHeight = 20.sp
    private val fontSizeInPx = with(defaultDensity) { fontSize.toPx() }
    private val lineHeightInPx = with(defaultDensity) { lineHeight.toPx() }

    /* single line even */

    @Test
    fun singleLine_even_trim_None() {
        val paragraph = singleLineParagraph(
            lineHeightTrim = Trim.None,
            lineHeightAlignment = Alignment.Center
        )

        val defaultFontMetrics = defaultFontMetrics()
        val diff = (lineHeightInPx - defaultFontMetrics.lineHeight()) / 2
        val expectedAscent = defaultFontMetrics.ascent - diff
        val expectedDescent = defaultFontMetrics.descent + diff

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(0)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)
        }
    }

    @Test
    fun singleLine_even_trim_LastLineBottom() {
        val paragraph = singleLineParagraph(
            lineHeightTrim = Trim.LastLineBottom,
            lineHeightAlignment = Alignment.Center
        )

        val defaultFontMetrics = defaultFontMetrics()
        val diff = (lineHeightInPx - defaultFontMetrics.lineHeight()) / 2
        val expectedAscent = defaultFontMetrics.ascent - diff
        val expectedDescent = defaultFontMetrics.descent

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx - diff)
            assertThat(getLineAscent(0)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)
        }
    }

    @Test
    fun singleLine_even_trim_FirstLineTop() {
        val paragraph = singleLineParagraph(
            lineHeightTrim = Trim.FirstLineTop,
            lineHeightAlignment = Alignment.Center
        )

        val defaultFontMetrics = defaultFontMetrics()
        val diff = (lineHeightInPx - defaultFontMetrics.lineHeight()) / 2
        val expectedAscent = defaultFontMetrics.ascent
        val expectedDescent = defaultFontMetrics.descent + diff

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx - diff)
            assertThat(getLineAscent(0)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)
        }
    }

    @Test
    fun singleLine_even_trim_Both() {
        val paragraph = singleLineParagraph(
            lineHeightTrim = Trim.Both,
            lineHeightAlignment = Alignment.Center
        )

        val defaultFontMetrics = defaultFontMetrics()
        val expectedAscent = defaultFontMetrics.ascent
        val expectedDescent = defaultFontMetrics.descent

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(defaultFontMetrics.lineHeight())
            assertThat(getLineAscent(0)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)
        }
    }

    /* single line top */

    @Test
    fun singleLine_top_trim_None() {
        val paragraph = singleLineParagraph(
            lineHeightTrim = Trim.None,
            lineHeightAlignment = Alignment.Top
        )

        val defaultFontMetrics = defaultFontMetrics()
        val diff = lineHeightInPx - defaultFontMetrics.lineHeight()
        val expectedAscent = defaultFontMetrics.ascent
        val expectedDescent = defaultFontMetrics.descent + diff

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(0)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)
        }
    }

    @Test
    fun singleLine_top_trim_LastLineBottom() {
        val paragraph = singleLineParagraph(
            lineHeightTrim = Trim.LastLineBottom,
            lineHeightAlignment = Alignment.Top
        )

        val defaultFontMetrics = defaultFontMetrics()
        val diff = lineHeightInPx - defaultFontMetrics.lineHeight()

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx - diff)
            assertThat(getLineAscent(0)).isEqualTo(defaultFontMetrics.ascent)
            assertThat(getLineDescent(0)).isEqualTo(defaultFontMetrics.descent)
        }
    }

    @Test
    fun singleLine_top_trim_FirstLineTop() {
        val paragraph = singleLineParagraph(
            lineHeightTrim = Trim.FirstLineTop,
            lineHeightAlignment = Alignment.Top
        )

        val defaultFontMetrics = defaultFontMetrics()
        val diff = lineHeightInPx - defaultFontMetrics.lineHeight()
        val expectedAscent = defaultFontMetrics.ascent
        val expectedDescent = defaultFontMetrics.descent + diff

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(0)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)
        }
    }

    @Test
    fun singleLine_top_trim_Both() {
        val paragraph = singleLineParagraph(
            lineHeightTrim = Trim.Both,
            lineHeightAlignment = Alignment.Top
        )

        val defaultFontMetrics = defaultFontMetrics()
        val expectedAscent = defaultFontMetrics.ascent
        val expectedDescent = defaultFontMetrics.descent

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(defaultFontMetrics.lineHeight())
            assertThat(getLineAscent(0)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)
        }
    }

    /* single line bottom */

    @Test
    fun singleLine_bottom_trim_None() {
        val paragraph = singleLineParagraph(
            lineHeightTrim = Trim.None,
            lineHeightAlignment = Alignment.Bottom
        )

        val defaultFontMetrics = defaultFontMetrics()
        val diff = lineHeightInPx - defaultFontMetrics.lineHeight()
        val expectedAscent = defaultFontMetrics.ascent - diff
        val expectedDescent = defaultFontMetrics.descent

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(0)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)
        }
    }

    @Test
    fun singleLine_bottom_trim_LastLineBottom() {
        val paragraph = singleLineParagraph(
            lineHeightTrim = Trim.LastLineBottom,
            lineHeightAlignment = Alignment.Bottom
        )

        val defaultFontMetrics = defaultFontMetrics()
        val diff = lineHeightInPx - defaultFontMetrics.lineHeight()

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(0)).isEqualTo(defaultFontMetrics.ascent - diff)
            assertThat(getLineDescent(0)).isEqualTo(defaultFontMetrics.descent)
        }
    }

    @Test
    fun singleLine_bottom_trim_FirstLineTop() {
        val paragraph = singleLineParagraph(
            lineHeightTrim = Trim.FirstLineTop,
            lineHeightAlignment = Alignment.Bottom
        )

        val defaultFontMetrics = defaultFontMetrics()
        val diff = lineHeightInPx - defaultFontMetrics.lineHeight()
        val expectedAscent = defaultFontMetrics.ascent
        val expectedDescent = defaultFontMetrics.descent

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx - diff)
            assertThat(getLineAscent(0)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)
        }
    }

    @Test
    fun singleLine_bottom_trim_Both() {
        val paragraph = singleLineParagraph(
            lineHeightTrim = Trim.Both,
            lineHeightAlignment = Alignment.Bottom
        )

        val defaultFontMetrics = defaultFontMetrics()
        val expectedAscent = defaultFontMetrics.ascent
        val expectedDescent = defaultFontMetrics.descent

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(defaultFontMetrics.lineHeight())
            assertThat(getLineAscent(0)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)
        }
    }

    /* single line proportional */

    @Test
    fun singleLine_proportional_trim_None() {
        val paragraph = singleLineParagraph(
            lineHeightTrim = Trim.None,
            lineHeightAlignment = Alignment.Proportional
        )

        val defaultFontMetrics = defaultFontMetrics()
        val descentDiff = proportionalDescentDiff(defaultFontMetrics)
        val ascentDiff = defaultFontMetrics.lineHeight() - descentDiff
        val expectedAscent = defaultFontMetrics.ascent - ascentDiff
        val expectedDescent = defaultFontMetrics.descent + descentDiff

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(0)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)
        }
    }

    @Test
    fun singleLine_proportional_trim_LastLineBottom() {
        val paragraph = singleLineParagraph(
            lineHeightTrim = Trim.LastLineBottom,
            lineHeightAlignment = Alignment.Proportional
        )

        val defaultFontMetrics = defaultFontMetrics()
        val descentDiff = proportionalDescentDiff(defaultFontMetrics)
        val ascentDiff = defaultFontMetrics.lineHeight() - descentDiff
        val expectedAscent = defaultFontMetrics.ascent - ascentDiff
        val expectedDescent = defaultFontMetrics.descent

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx - descentDiff)
            assertThat(getLineAscent(0)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)
        }
    }

    @Test
    fun singleLine_proportional_trim_FirstLineTop() {
        val paragraph = singleLineParagraph(
            lineHeightTrim = Trim.FirstLineTop,
            lineHeightAlignment = Alignment.Proportional
        )

        val defaultFontMetrics = defaultFontMetrics()
        val descentDiff = proportionalDescentDiff(defaultFontMetrics)
        val ascentDiff = defaultFontMetrics.lineHeight() - descentDiff
        val expectedAscent = defaultFontMetrics.ascent
        val expectedDescent = defaultFontMetrics.descent + descentDiff

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx - ascentDiff)
            assertThat(getLineAscent(0)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)
        }
    }

    @Test
    fun singleLine_proportional_trim_Both() {
        val paragraph = singleLineParagraph(
            lineHeightTrim = Trim.Both,
            lineHeightAlignment = Alignment.Proportional
        )

        val defaultFontMetrics = defaultFontMetrics()
        val expectedAscent = defaultFontMetrics.ascent
        val expectedDescent = defaultFontMetrics.descent

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(defaultFontMetrics.lineHeight())
            assertThat(getLineAscent(0)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)
        }
    }

    /* multi line even */

    @Test
    fun multiLine_even_trim_None() {
        val paragraph = multiLineParagraph(
            lineHeightTrim = Trim.None,
            lineHeightAlignment = Alignment.Center
        )

        val defaultFontMetrics = defaultFontMetrics()
        val diff = (lineHeightInPx - defaultFontMetrics.lineHeight()) / 2
        val expectedAscent = defaultFontMetrics.ascent - diff
        val expectedDescent = defaultFontMetrics.descent + diff

        with(paragraph) {
            for (line in 0 until lineCount) {
                assertThat(getLineHeight(line)).isEqualTo(lineHeightInPx)
                assertThat(getLineAscent(line)).isEqualTo(expectedAscent)
                assertThat(getLineDescent(line)).isEqualTo(expectedDescent)
            }

            assertThat(getLineBaseline(1) - getLineBaseline(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineBaseline(2) - getLineBaseline(1)).isEqualTo(lineHeightInPx)
        }
    }

    @Test
    fun multiLine_even_trim_LastLineBottom() {
        val paragraph = multiLineParagraph(
            lineHeightTrim = Trim.LastLineBottom,
            lineHeightAlignment = Alignment.Center
        )

        val defaultFontMetrics = defaultFontMetrics()
        val diff = (lineHeightInPx - defaultFontMetrics.lineHeight()) / 2
        val expectedAscent = defaultFontMetrics.ascent - diff
        val expectedDescent = defaultFontMetrics.descent + diff

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(0)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(1)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(1)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(1)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(2)).isEqualTo(lineHeightInPx - diff)
            assertThat(getLineAscent(2)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(2)).isEqualTo(defaultFontMetrics.descent)

            assertThat(getLineBaseline(1) - getLineBaseline(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineBaseline(2) - getLineBaseline(1)).isEqualTo(lineHeightInPx)
        }
    }

    @Test
    fun multiLine_even_trim_FirstLineTop() {
        val paragraph = multiLineParagraph(
            lineHeightTrim = Trim.FirstLineTop,
            lineHeightAlignment = Alignment.Center
        )

        val defaultFontMetrics = defaultFontMetrics()
        val diff = (lineHeightInPx - defaultFontMetrics.lineHeight()) / 2
        val expectedAscent = defaultFontMetrics.ascent - diff
        val expectedDescent = defaultFontMetrics.descent + diff

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx - diff)
            assertThat(getLineAscent(0)).isEqualTo(defaultFontMetrics.ascent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(1)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(1)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(1)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(2)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(2)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(2)).isEqualTo(expectedDescent)

            assertThat(getLineBaseline(1) - getLineBaseline(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineBaseline(2) - getLineBaseline(1)).isEqualTo(lineHeightInPx)
        }
    }

    @Test
    fun multiLine_even_trim_Both() {
        val paragraph = multiLineParagraph(
            lineHeightTrim = Trim.Both,
            lineHeightAlignment = Alignment.Center
        )

        val defaultFontMetrics = defaultFontMetrics()
        val diff = (lineHeightInPx - defaultFontMetrics.lineHeight()) / 2
        val expectedAscent = defaultFontMetrics.ascent - diff
        val expectedDescent = defaultFontMetrics.descent + diff

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx - diff)
            assertThat(getLineAscent(0)).isEqualTo(defaultFontMetrics.ascent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(1)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(1)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(1)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(2)).isEqualTo(lineHeightInPx - diff)
            assertThat(getLineAscent(2)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(2)).isEqualTo(defaultFontMetrics.descent)

            assertThat(getLineBaseline(1) - getLineBaseline(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineBaseline(2) - getLineBaseline(1)).isEqualTo(lineHeightInPx)
        }
    }

    /* multi line top */

    @Test
    fun multiLine_top_trim_None() {
        val paragraph = multiLineParagraph(
            lineHeightTrim = Trim.None,
            lineHeightAlignment = Alignment.Top
        )

        val defaultFontMetrics = defaultFontMetrics()
        val diff = lineHeightInPx - defaultFontMetrics.lineHeight()
        val expectedAscent = defaultFontMetrics.ascent
        val expectedDescent = defaultFontMetrics.descent + diff

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(0)).isEqualTo(defaultFontMetrics.ascent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(1)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(1)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(1)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(2)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(2)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(2)).isEqualTo(expectedDescent)

            assertThat(getLineBaseline(1) - getLineBaseline(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineBaseline(2) - getLineBaseline(1)).isEqualTo(lineHeightInPx)
        }
    }

    @Test
    fun multiLine_top_trim_LastLineBottom() {
        val paragraph = multiLineParagraph(
            lineHeightTrim = Trim.LastLineBottom,
            lineHeightAlignment = Alignment.Top
        )

        val defaultFontMetrics = defaultFontMetrics()
        val diff = lineHeightInPx - defaultFontMetrics.lineHeight()
        val expectedAscent = defaultFontMetrics.ascent
        val expectedDescent = defaultFontMetrics.descent + diff

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(0)).isEqualTo(defaultFontMetrics.ascent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(1)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(1)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(1)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(2)).isEqualTo(lineHeightInPx - diff)
            assertThat(getLineAscent(2)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(2)).isEqualTo(defaultFontMetrics.descent)

            assertThat(getLineBaseline(1) - getLineBaseline(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineBaseline(2) - getLineBaseline(1)).isEqualTo(lineHeightInPx)
        }
    }

    @Test
    fun multiLine_top_trim_FirstLineTop() {
        val paragraph = multiLineParagraph(
            lineHeightTrim = Trim.FirstLineTop,
            lineHeightAlignment = Alignment.Top
        )

        val defaultFontMetrics = defaultFontMetrics()
        val diff = lineHeightInPx - defaultFontMetrics.lineHeight()
        val expectedAscent = defaultFontMetrics.ascent
        val expectedDescent = defaultFontMetrics.descent + diff

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(0)).isEqualTo(defaultFontMetrics.ascent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(1)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(1)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(1)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(2)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(2)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(2)).isEqualTo(expectedDescent)

            assertThat(getLineBaseline(1) - getLineBaseline(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineBaseline(2) - getLineBaseline(1)).isEqualTo(lineHeightInPx)
        }
    }

    @Test
    fun multiLine_top_trim_Both() {
        val paragraph = multiLineParagraph(
            lineHeightTrim = Trim.Both,
            lineHeightAlignment = Alignment.Top
        )

        val defaultFontMetrics = defaultFontMetrics()
        val diff = lineHeightInPx - defaultFontMetrics.lineHeight()
        val expectedAscent = defaultFontMetrics.ascent
        val expectedDescent = defaultFontMetrics.descent + diff

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(0)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(1)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(1)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(1)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(2)).isEqualTo(lineHeightInPx - diff)
            assertThat(getLineAscent(2)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(2)).isEqualTo(defaultFontMetrics.descent)

            assertThat(getLineBaseline(1) - getLineBaseline(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineBaseline(2) - getLineBaseline(1)).isEqualTo(lineHeightInPx)
        }
    }

    /* multi line bottom */

    @Test
    fun multiLine_bottom_trim_None() {
        val paragraph = multiLineParagraph(
            lineHeightTrim = Trim.None,
            lineHeightAlignment = Alignment.Bottom
        )

        val defaultFontMetrics = defaultFontMetrics()
        val diff = lineHeightInPx - defaultFontMetrics.lineHeight()
        val expectedAscent = defaultFontMetrics.ascent - diff
        val expectedDescent = defaultFontMetrics.descent

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(0)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(1)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(1)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(1)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(2)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(2)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(2)).isEqualTo(expectedDescent)

            assertThat(getLineBaseline(1) - getLineBaseline(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineBaseline(2) - getLineBaseline(1)).isEqualTo(lineHeightInPx)
        }
    }

    @Test
    fun multiLine_bottom_trim_LastLineBottom() {
        val paragraph = multiLineParagraph(
            lineHeightTrim = Trim.LastLineBottom,
            lineHeightAlignment = Alignment.Bottom
        )

        val defaultFontMetrics = defaultFontMetrics()
        val diff = lineHeightInPx - defaultFontMetrics.lineHeight()
        val expectedAscent = defaultFontMetrics.ascent - diff
        val expectedDescent = defaultFontMetrics.descent

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(0)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(1)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(1)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(1)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(2)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(2)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(2)).isEqualTo(defaultFontMetrics.descent)

            assertThat(getLineBaseline(1) - getLineBaseline(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineBaseline(2) - getLineBaseline(1)).isEqualTo(lineHeightInPx)
        }
    }

    @Test
    fun multiLine_bottom_trim_FirstLineTop() {
        val paragraph = multiLineParagraph(
            lineHeightTrim = Trim.FirstLineTop,
            lineHeightAlignment = Alignment.Bottom
        )

        val defaultFontMetrics = defaultFontMetrics()
        val diff = lineHeightInPx - defaultFontMetrics.lineHeight()
        val expectedAscent = defaultFontMetrics.ascent - diff
        val expectedDescent = defaultFontMetrics.descent

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx - diff)
            assertThat(getLineAscent(0)).isEqualTo(defaultFontMetrics.ascent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(1)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(1)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(1)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(2)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(2)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(2)).isEqualTo(expectedDescent)

            assertThat(getLineBaseline(1) - getLineBaseline(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineBaseline(2) - getLineBaseline(1)).isEqualTo(lineHeightInPx)
        }
    }

    @Test
    fun multiLine_bottom_trim_Both() {
        val paragraph = multiLineParagraph(
            lineHeightTrim = Trim.Both,
            lineHeightAlignment = Alignment.Bottom
        )

        val defaultFontMetrics = defaultFontMetrics()
        val diff = lineHeightInPx - defaultFontMetrics.lineHeight()
        val expectedAscent = defaultFontMetrics.ascent - diff
        val expectedDescent = defaultFontMetrics.descent

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx - diff)
            assertThat(getLineAscent(0)).isEqualTo(defaultFontMetrics.ascent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(1)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(1)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(1)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(2)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(2)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(2)).isEqualTo(defaultFontMetrics.descent)

            assertThat(getLineBaseline(1) - getLineBaseline(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineBaseline(2) - getLineBaseline(1)).isEqualTo(lineHeightInPx)
        }
    }

    /* multi line proportional */

    @Test
    fun multiLine_proportional_trim_None() {
        val paragraph = multiLineParagraph(
            lineHeightTrim = Trim.None,
            lineHeightAlignment = Alignment.Proportional
        )

        val defaultFontMetrics = defaultFontMetrics()
        val descentDiff = proportionalDescentDiff(defaultFontMetrics)
        val ascentDiff = defaultFontMetrics.lineHeight() - descentDiff
        val expectedAscent = defaultFontMetrics.ascent - ascentDiff
        val expectedDescent = defaultFontMetrics.descent + descentDiff

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(0)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(1)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(1)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(1)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(2)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(2)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(2)).isEqualTo(expectedDescent)

            assertThat(getLineBaseline(1) - getLineBaseline(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineBaseline(2) - getLineBaseline(1)).isEqualTo(lineHeightInPx)
        }
    }

    @Test
    fun multiLine_proportional_trim_LastLineBottom() {
        val paragraph = multiLineParagraph(
            lineHeightTrim = Trim.LastLineBottom,
            lineHeightAlignment = Alignment.Proportional
        )

        val defaultFontMetrics = defaultFontMetrics()
        val descentDiff = proportionalDescentDiff(defaultFontMetrics)
        val ascentDiff = defaultFontMetrics.lineHeight() - descentDiff
        val expectedAscent = defaultFontMetrics.ascent - ascentDiff
        val expectedDescent = defaultFontMetrics.descent + descentDiff

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(0)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(1)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(1)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(1)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(2)).isEqualTo(lineHeightInPx - descentDiff)
            assertThat(getLineAscent(2)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(2)).isEqualTo(defaultFontMetrics.descent)

            assertThat(getLineBaseline(1) - getLineBaseline(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineBaseline(2) - getLineBaseline(1)).isEqualTo(lineHeightInPx)
        }
    }

    @Test
    fun multiLine_proportional_trim_FirstLineTop() {
        val paragraph = multiLineParagraph(
            lineHeightTrim = Trim.FirstLineTop,
            lineHeightAlignment = Alignment.Proportional
        )

        val defaultFontMetrics = defaultFontMetrics()
        val descentDiff = proportionalDescentDiff(defaultFontMetrics)
        val ascentDiff = defaultFontMetrics.lineHeight() - descentDiff
        val expectedAscent = defaultFontMetrics.ascent - ascentDiff
        val expectedDescent = defaultFontMetrics.descent + descentDiff

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx - ascentDiff)
            assertThat(getLineAscent(0)).isEqualTo(defaultFontMetrics.ascent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(1)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(1)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(1)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(2)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(2)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(2)).isEqualTo(expectedDescent)

            assertThat(getLineBaseline(1) - getLineBaseline(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineBaseline(2) - getLineBaseline(1)).isEqualTo(lineHeightInPx)
        }
    }

    @Test
    fun multiLine_proportional_trim_Both() {
        val paragraph = multiLineParagraph(
            lineHeightTrim = Trim.Both,
            lineHeightAlignment = Alignment.Proportional
        )

        val defaultFontMetrics = defaultFontMetrics()
        val descentDiff = proportionalDescentDiff(defaultFontMetrics)
        val ascentDiff = defaultFontMetrics.lineHeight() - descentDiff
        val expectedAscent = defaultFontMetrics.ascent - ascentDiff
        val expectedDescent = defaultFontMetrics.descent + descentDiff

        with(paragraph) {
            assertThat(getLineHeight(0)).isEqualTo(lineHeightInPx - ascentDiff)
            assertThat(getLineAscent(0)).isEqualTo(defaultFontMetrics.ascent)
            assertThat(getLineDescent(0)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(1)).isEqualTo(lineHeightInPx)
            assertThat(getLineAscent(1)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(1)).isEqualTo(expectedDescent)

            assertThat(getLineHeight(2)).isEqualTo(lineHeightInPx - descentDiff)
            assertThat(getLineAscent(2)).isEqualTo(expectedAscent)
            assertThat(getLineDescent(2)).isEqualTo(defaultFontMetrics.descent)

            assertThat(getLineBaseline(1) - getLineBaseline(0)).isEqualTo(lineHeightInPx)
            assertThat(getLineBaseline(2) - getLineBaseline(1)).isEqualTo(lineHeightInPx)
        }
    }

    @Test
    fun lastLineEmptyTextHasSameLineHeightAsNonEmptyText() {
        assertEmptyLineMetrics("", "a")
        assertEmptyLineMetrics("\n", "a\na")
        assertEmptyLineMetrics("a\n", "a\na")
        assertEmptyLineMetrics("\na", "a\na")
        assertEmptyLineMetrics("\na\na", "a\na\na")
        assertEmptyLineMetrics("a\na\n", "a\na\na")
    }

    private fun assertEmptyLineMetrics(textWithEmptyLine: String, textWithoutEmptyLine: String) {
        val textStyle = TextStyle(
            lineHeightStyle = LineHeightStyle(
                trim = Trim.None,
                alignment = Alignment.Proportional
            ),
            platformStyle = @Suppress("DEPRECATION") PlatformTextStyle(
                includeFontPadding = false
            )
        )

        val paragraphWithEmptyLastLine = simpleParagraph(
            text = textWithEmptyLine,
            style = textStyle
        ) as AndroidParagraph

        val otherParagraph = simpleParagraph(
            text = textWithoutEmptyLine,
            style = textStyle
        ) as AndroidParagraph

        with(paragraphWithEmptyLastLine) {
            for (line in 0 until lineCount) {
                assertThat(height).isEqualTo(otherParagraph.height)
                assertThat(getLineTop(line)).isEqualTo(otherParagraph.getLineTop(line))
                assertThat(getLineBottom(line)).isEqualTo(otherParagraph.getLineBottom(line))
                assertThat(getLineHeight(line)).isEqualTo(otherParagraph.getLineHeight(line))
                assertThat(getLineAscent(line)).isEqualTo(otherParagraph.getLineAscent(line))
                assertThat(getLineDescent(line)).isEqualTo(otherParagraph.getLineDescent(line))
                assertThat(getLineBaseline(line)).isEqualTo(otherParagraph.getLineBaseline(line))
            }
        }
    }

    private fun singleLineParagraph(
        lineHeightTrim: Trim,
        lineHeightAlignment: Alignment,
        text: String = "AAA"
    ): AndroidParagraph {
        val textStyle = TextStyle(
            lineHeightStyle = LineHeightStyle(
                trim = lineHeightTrim,
                alignment = lineHeightAlignment
            )
        )

        val paragraph = simpleParagraph(
            text = text,
            style = textStyle,
            width = text.length * fontSizeInPx
        ) as AndroidParagraph

        assertThat(paragraph.lineCount).isEqualTo(1)

        return paragraph
    }

    @Suppress("DEPRECATION")
    private fun multiLineParagraph(
        lineHeightTrim: Trim,
        lineHeightAlignment: Alignment,
    ): AndroidParagraph {
        val lineCount = 3
        val word = "AAA"
        val text = "AAA".repeat(lineCount)

        val textStyle = TextStyle(
            lineHeightStyle = LineHeightStyle(
                trim = lineHeightTrim,
                alignment = lineHeightAlignment
            ),
            platformStyle = @Suppress("DEPRECATION") PlatformTextStyle(
                includeFontPadding = false
            )
        )

        val paragraph = simpleParagraph(
            text = text,
            style = textStyle,
            width = word.length * fontSizeInPx
        ) as AndroidParagraph

        assertThat(paragraph.lineCount).isEqualTo(lineCount)

        return paragraph
    }

    private fun simpleParagraph(
        text: String = "",
        style: TextStyle? = null,
        maxLines: Int = Int.MAX_VALUE,
        ellipsis: Boolean = false,
        spanStyles: List> = listOf(),
        width: Float = Float.MAX_VALUE
    ): Paragraph {
        return Paragraph(
            text = text,
            spanStyles = spanStyles,
            style = TextStyle(
                fontFamily = fontFamilyMeasureFont,
                fontSize = fontSize,
                lineHeight = lineHeight,
                platformStyle = @Suppress("DEPRECATION") PlatformTextStyle(
                    includeFontPadding = false
                )
            ).merge(style),
            maxLines = maxLines,
            ellipsis = ellipsis,
            constraints = Constraints(maxWidth = width.ceilToInt()),
            density = defaultDensity,
            fontFamilyResolver = UncachedFontFamilyResolver(context)
        )
    }

    private fun defaultFontMetrics(): FontMetricsInt {
        return (simpleParagraph() as AndroidParagraph).paragraphIntrinsics.textPaint.fontMetricsInt
    }

    private fun proportionalDescentDiff(fontMetrics: FontMetricsInt): Int {
        val ascent = abs(fontMetrics.ascent.toFloat())
        val ascentRatio = ascent / fontMetrics.lineHeight()
        return ceil(fontMetrics.lineHeight() * (1f - ascentRatio)).toInt()
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy