1 /* 2 * Copyright 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.compose.foundation.text.modifiers 18 19 import androidx.compose.foundation.text.TEST_FONT_FAMILY 20 import androidx.compose.foundation.text.toIntPx 21 import androidx.compose.ui.geometry.Offset 22 import androidx.compose.ui.graphics.Canvas 23 import androidx.compose.ui.text.AnnotatedString 24 import androidx.compose.ui.text.SpanStyle 25 import androidx.compose.ui.text.TextPainter 26 import androidx.compose.ui.text.TextStyle 27 import androidx.compose.ui.text.font.createFontFamilyResolver 28 import androidx.compose.ui.unit.Constraints 29 import androidx.compose.ui.unit.Density 30 import androidx.compose.ui.unit.LayoutDirection 31 import androidx.compose.ui.unit.sp 32 import androidx.test.ext.junit.runners.AndroidJUnit4 33 import androidx.test.filters.SmallTest 34 import androidx.test.platform.app.InstrumentationRegistry 35 import com.google.common.truth.IntegerSubject 36 import com.google.common.truth.Truth.assertThat 37 import kotlin.math.floor 38 import org.junit.Test 39 import org.junit.runner.RunWith 40 41 @RunWith(AndroidJUnit4::class) 42 @SmallTest 43 class TextLayoutResultIntegrationTest { 44 45 private val fontFamily = TEST_FONT_FAMILY 46 private val density = Density(density = 1f) 47 private val context = InstrumentationRegistry.getInstrumentation().context 48 private val fontFamilyResolver = createFontFamilyResolver(context) 49 private val layoutDirection = LayoutDirection.Ltr 50 51 @Test width_getternull52 fun width_getter() { 53 with(density) { 54 val fontSize = 20.sp 55 val text = "Hello" 56 val spanStyle = SpanStyle(fontSize = fontSize, fontFamily = fontFamily) 57 val annotatedString = AnnotatedString(text, spanStyle) 58 val textDelegate = 59 MultiParagraphLayoutCache( 60 text = annotatedString, 61 style = TextStyle.Default, 62 fontFamilyResolver = fontFamilyResolver 63 ) 64 .also { it.density = this } 65 66 textDelegate.layoutWithConstraints(Constraints(0, 200), layoutDirection) 67 val layoutResult = textDelegate.textLayoutResult 68 69 assertThat(layoutResult.size.width).isEqualTo((fontSize.toPx() * text.length).toIntPx()) 70 } 71 } 72 73 @Test width_getter_with_small_widthnull74 fun width_getter_with_small_width() { 75 val text = "Hello" 76 val width = 80 77 val spanStyle = SpanStyle(fontSize = 20.sp, fontFamily = fontFamily) 78 val annotatedString = AnnotatedString(text, spanStyle) 79 val textDelegate = 80 MultiParagraphLayoutCache( 81 text = annotatedString, 82 style = TextStyle.Default, 83 fontFamilyResolver = fontFamilyResolver 84 ) 85 .also { it.density = density } 86 87 textDelegate.layoutWithConstraints(Constraints(maxWidth = width), layoutDirection) 88 val layoutResult = textDelegate.textLayoutResult 89 90 assertThat(layoutResult.size.width).isEqualTo(width) 91 } 92 93 @Test height_getternull94 fun height_getter() { 95 with(density) { 96 val fontSize = 20.sp 97 val spanStyle = SpanStyle(fontSize = fontSize, fontFamily = fontFamily) 98 val text = "hello" 99 val annotatedString = AnnotatedString(text, spanStyle) 100 val textDelegate = 101 MultiParagraphLayoutCache( 102 text = annotatedString, 103 style = TextStyle.Default, 104 fontFamilyResolver = fontFamilyResolver 105 ) 106 .also { it.density = this } 107 108 textDelegate.layoutWithConstraints(Constraints(), layoutDirection) 109 val layoutResult = textDelegate.textLayoutResult 110 111 assertThat(layoutResult.size.height).isEqualTo((fontSize.toPx()).toIntPx()) 112 } 113 } 114 115 @Test layout_build_layoutResultnull116 fun layout_build_layoutResult() { 117 val textDelegate = 118 MultiParagraphLayoutCache( 119 text = AnnotatedString("hello"), 120 style = TextStyle.Default, 121 fontFamilyResolver = fontFamilyResolver 122 ) 123 .also { it.density = density } 124 125 textDelegate.layoutWithConstraints(Constraints(0, 20), layoutDirection) 126 val layoutResult = textDelegate.textLayoutResult 127 128 assertThat(layoutResult).isNotNull() 129 } 130 IntegerSubjectnull131 private fun IntegerSubject.isZero() { 132 this.isEqualTo(0) 133 } 134 135 @Test getPositionForOffset_First_Characternull136 fun getPositionForOffset_First_Character() { 137 val text = "Hello" 138 val annotatedString = 139 AnnotatedString(text, SpanStyle(fontSize = 20.sp, fontFamily = fontFamily)) 140 141 val textDelegate = 142 MultiParagraphLayoutCache( 143 text = annotatedString, 144 style = TextStyle.Default, 145 fontFamilyResolver = fontFamilyResolver 146 ) 147 .also { it.density = density } 148 textDelegate.layoutWithConstraints(Constraints(), layoutDirection) 149 val layoutResult = textDelegate.textLayoutResult 150 151 val selection = layoutResult.getOffsetForPosition(Offset.Zero) 152 153 assertThat(selection).isZero() 154 } 155 156 @Test getPositionForOffset_other_Characternull157 fun getPositionForOffset_other_Character() { 158 with(density) { 159 val fontSize = 20.sp 160 val characterIndex = 2 // Start from 0. 161 val text = "Hello" 162 163 val annotatedString = 164 AnnotatedString(text, SpanStyle(fontSize = fontSize, fontFamily = fontFamily)) 165 166 val textDelegate = 167 MultiParagraphLayoutCache( 168 text = annotatedString, 169 style = TextStyle.Default, 170 fontFamilyResolver = fontFamilyResolver 171 ) 172 .also { it.density = this } 173 textDelegate.layoutWithConstraints(Constraints(), layoutDirection) 174 val layoutResult = textDelegate.textLayoutResult 175 176 val selection = 177 layoutResult.getOffsetForPosition( 178 position = Offset((fontSize.toPx() * characterIndex + 1), 0f) 179 ) 180 181 assertThat(selection).isEqualTo(characterIndex) 182 } 183 } 184 185 @Test hasOverflowShaderFalsenull186 fun hasOverflowShaderFalse() { 187 val text = "Hello" 188 val spanStyle = SpanStyle(fontSize = 20.sp, fontFamily = fontFamily) 189 val annotatedString = AnnotatedString(text, spanStyle) 190 val textDelegate = 191 MultiParagraphLayoutCache( 192 text = annotatedString, 193 style = TextStyle.Default, 194 fontFamilyResolver = fontFamilyResolver 195 ) 196 .also { it.density = density } 197 198 textDelegate.layoutWithConstraints(Constraints(), layoutDirection) 199 val layoutResult = textDelegate.textLayoutResult 200 201 assertThat(layoutResult.hasVisualOverflow).isFalse() 202 203 // paint should not throw exception 204 TextPainter.paint(Canvas(android.graphics.Canvas()), layoutResult) 205 } 206 207 @Test didOverflowHeight_isTrue_when_maxLines_exceedednull208 fun didOverflowHeight_isTrue_when_maxLines_exceeded() { 209 val text = "HelloHellowHelloHello" 210 val spanStyle = SpanStyle(fontSize = 20.sp, fontFamily = fontFamily) 211 val annotatedString = AnnotatedString(text, spanStyle) 212 val maxLines = 3 213 214 val textDelegate = 215 MultiParagraphLayoutCache( 216 text = annotatedString, 217 style = TextStyle.Default, 218 fontFamilyResolver = fontFamilyResolver, 219 maxLines = maxLines 220 ) 221 .also { it.density = density } 222 223 textDelegate.layoutWithConstraints(Constraints.fixed(0, 0), LayoutDirection.Ltr) 224 // Tries to make 5 lines of text, which exceeds the given maxLines(3). 225 val maxWidth = textDelegate.maxIntrinsicWidth(LayoutDirection.Ltr) / 5 226 textDelegate.layoutWithConstraints(Constraints(maxWidth = maxWidth), layoutDirection) 227 val layoutResult = textDelegate.textLayoutResult 228 229 assertThat(layoutResult.didOverflowHeight).isTrue() 230 } 231 232 @Test didOverflowHeight_isFalse_when_maxLines_notExceedednull233 fun didOverflowHeight_isFalse_when_maxLines_notExceeded() { 234 val text = "HelloHellowHelloHello" 235 val spanStyle = SpanStyle(fontSize = 20.sp, fontFamily = fontFamily) 236 val annotatedString = AnnotatedString(text, spanStyle) 237 val maxLines = 10 238 239 val textDelegate = 240 MultiParagraphLayoutCache( 241 text = annotatedString, 242 style = TextStyle.Default, 243 fontFamilyResolver = fontFamilyResolver, 244 maxLines = maxLines 245 ) 246 .also { it.density = density } 247 248 textDelegate.layoutWithConstraints(Constraints.fixed(0, 0), LayoutDirection.Ltr) 249 // Tries to make 5 lines of text, which doesn't exceed the given maxLines(10). 250 val maxWidth = textDelegate.maxIntrinsicWidth(LayoutDirection.Ltr) / 5 251 textDelegate.layoutWithConstraints(Constraints(maxWidth = maxWidth), layoutDirection) 252 val layoutResult = textDelegate.textLayoutResult 253 254 assertThat(layoutResult.didOverflowHeight).isFalse() 255 } 256 257 @Test didOverflowHeight_isTrue_when_maxHeight_exceedednull258 fun didOverflowHeight_isTrue_when_maxHeight_exceeded() { 259 val text = "HelloHellowHelloHello" 260 val spanStyle = SpanStyle(fontSize = 20.sp, fontFamily = fontFamily) 261 val annotatedString = AnnotatedString(text, spanStyle) 262 263 val textDelegate = 264 MultiParagraphLayoutCache( 265 text = annotatedString, 266 style = TextStyle.Default, 267 fontFamilyResolver = fontFamilyResolver, 268 ) 269 .also { it.density = density } 270 271 textDelegate.layoutWithConstraints(Constraints(), layoutDirection) 272 273 val maxIntrinsicsHeight = textDelegate.textLayoutResult.multiParagraph.height 274 275 // Make maxHeight smaller than needed. 276 val maxHeight = floor(maxIntrinsicsHeight / 2).toInt() 277 textDelegate.layoutWithConstraints(Constraints(maxHeight = maxHeight), layoutDirection) 278 val layoutResult = textDelegate.textLayoutResult 279 280 assertThat(layoutResult.didOverflowHeight).isTrue() 281 } 282 283 @Test didOverflowHeight_isFalse_when_maxHeight_notExceedednull284 fun didOverflowHeight_isFalse_when_maxHeight_notExceeded() { 285 val text = "HelloHellowHelloHello" 286 val spanStyle = SpanStyle(fontSize = 20.sp, fontFamily = fontFamily) 287 val annotatedString = AnnotatedString(text, spanStyle) 288 289 val textDelegate = 290 MultiParagraphLayoutCache( 291 text = annotatedString, 292 style = TextStyle.Default, 293 fontFamilyResolver = fontFamilyResolver, 294 ) 295 .also { it.density = density } 296 297 textDelegate.layoutWithConstraints(Constraints(), layoutDirection) 298 val maxIntrinsicsHeight = textDelegate.textLayoutResult.multiParagraph.height 299 300 // Make max height larger than the needed. 301 val maxHeight = floor(maxIntrinsicsHeight * 2).toInt() 302 textDelegate.layoutWithConstraints(Constraints(maxHeight = maxHeight), layoutDirection) 303 val layoutResult = textDelegate.textLayoutResult 304 305 assertThat(layoutResult.didOverflowHeight).isFalse() 306 } 307 } 308