Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -883,15 +883,19 @@ internal object TextLayoutManager {
// Layout.getDesiredWidth is a full measuring pass over the text, so only run it when its
// result can affect the layout width. It cannot when the width is EXACTLY, nor when the text
// was boring but did not fit: boring.width is the unwrapped width of the same text and we only
// get here if it exceeds floor(width), so min(desiredWidth, floor(width)) is floor(width).
// get here if it exceeds floor(width), so min(desiredWidth, ceil(width)) is ceil(width).
//
// AT_MOST wraps at ceil(width), the same width EXACTLY uses when the text is drawn. Wrapping at
// floor(width) here measured text that fits in (floor(width), width] as one line more than it
// is drawn with. calculateWidth() clamps the reported width to the AT_MOST constraint.
val layoutWidth =
when {
widthYogaMeasureMode == YogaMeasureMode.EXACTLY -> ceil(width).toInt()
widthYogaMeasureMode == YogaMeasureMode.AT_MOST && boring != null -> floor(width).toInt()
widthYogaMeasureMode == YogaMeasureMode.AT_MOST && boring != null -> ceil(width).toInt()
else -> {
val desiredWidth = ceil(Layout.getDesiredWidth(text, paint)).toInt()
if (widthYogaMeasureMode == YogaMeasureMode.AT_MOST) {
min(desiredWidth, floor(width).toInt())
min(desiredWidth, ceil(width).toInt())
} else {
desiredWidth
}
Expand Down Expand Up @@ -1708,6 +1712,11 @@ internal object TextLayoutManager {
return width
}

// The layout may be up to a subpixel wider than an AT_MOST width, see createLayout().
if (widthYogaMeasureMode == YogaMeasureMode.AT_MOST) {
return min(layout.width.toFloat(), width)
}

return layout.width.toFloat()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.views.text

import android.annotation.SuppressLint
import android.text.BoringLayout
import android.text.Layout
import android.text.SpannableString
import android.text.TextPaint
import com.facebook.yoga.YogaMeasureMode
import kotlin.math.floor
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.GraphicsMode

@RunWith(RobolectricTestRunner::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
class TextLayoutManagerAtMostFractionalWidthTest {

@Test
fun `AT_MOST measure and EXACTLY draw at the same fractional width have the same line count`() {
val text = SpannableString("Keep watching this text")
val paint = TextPaint(TextPaint.ANTI_ALIAS_FLAG).apply { textSize = 16f }
val desiredWidth = Layout.getDesiredWidth(text, paint)
// A width the text fits in, but whose floor is smaller than the text.
val width = floor(desiredWidth) + (desiredWidth - floor(desiredWidth) + 1f) / 2f
assertThat(floor(width)).isLessThan(desiredWidth)
assertThat(width).isGreaterThanOrEqualTo(desiredWidth)

val measured = invokeCreateLayout(text, width, YogaMeasureMode.AT_MOST, paint)
val drawn = invokeCreateLayout(text, width, YogaMeasureMode.EXACTLY, paint)

assertThat(measured.lineCount).isEqualTo(drawn.lineCount)
}

@SuppressLint("InlinedApi")
private fun invokeCreateLayout(
text: SpannableString,
width: Float,
mode: YogaMeasureMode,
paint: TextPaint,
): Layout =
TextLayoutManager.createLayout(
text,
BoringLayout.isBoring(text, paint),
width,
mode,
/* includeFontPadding = */ false,
/* textBreakStrategy = */ Layout.BREAK_STRATEGY_HIGH_QUALITY,
/* hyphenationFrequency = */ Layout.HYPHENATION_FREQUENCY_NONE,
Layout.Alignment.ALIGN_NORMAL,
/* justificationMode = */ 0,
/* ellipsizeMode = */ null,
/* maxNumberOfLines = */ 2,
paint,
)
}
Loading