Skip to content

Commit 9b76677

Browse files
feat: lineHeight support for EnrichedText (#724)
# Summary Fixes: #608 Add support for `lineHeight` on mobile. On web It is already supported. ## Test Plan 1. Display some content inside `EnrichedText` 2. Change `lineHeight` in style prop for `EnrichedText` 3. `lineHeight` should be applied properly ## Screenshots / Videos ## Compatibility | OS | Implemented | | ------- | :---------: | | iOS | ✅ | | Android | ✅ | | Web | ❌ | ## Checklist - [x] E2E tests are passing - [ ] Required E2E tests have been added (if applicable)
1 parent 2b0d681 commit 9b76677

8 files changed

Lines changed: 75 additions & 8 deletions

File tree

android/src/main/java/com/swmansion/enriched/text/EnrichedTextView.kt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import com.swmansion.enriched.common.pixelFromSpOrDp
3030
import com.swmansion.enriched.text.spans.EnrichedTextImageSpan
3131
import com.swmansion.enriched.text.spans.interfaces.EnrichedTextClickableSpan
3232
import com.swmansion.enriched.text.spans.interfaces.EnrichedTextSpan
33+
import com.swmansion.enriched.textinput.spans.EnrichedLineHeightSpan
3334
import kotlin.math.ceil
3435

3536
class EnrichedTextView : AppCompatTextView {
@@ -41,13 +42,15 @@ class EnrichedTextView : AppCompatTextView {
4142
private var fontWeight: Int = ReactConstants.UNSET
4243
private var fontSize: Float = EnrichedConstants.TEXT_DEFAULT_FONT_SIZE
4344
private var fontSizeRaw: Float? = null
45+
private var lineHeight: Float? = null
4446
private var htmlStyleMap: ReadableMap? = null
4547
var allowFontScaling: Boolean = EnrichedConstants.ALLOW_FONT_SCALING_DEFAULT
4648
set(value) {
4749
if (field == value) return
4850
field = value
4951
fontSizeRaw?.let { setFontSize(it) }
5052
htmlStyleMap?.let { setHtmlStyle(it) }
53+
applyLineSpacing()
5154
}
5255

5356
private var enrichedStyle: EnrichedTextStyle? = null
@@ -185,6 +188,7 @@ class EnrichedTextView : AppCompatTextView {
185188
parsedText = null
186189
this.text = text
187190
}
191+
applyLineSpacing()
188192
}
189193

190194
private fun parseText(
@@ -305,6 +309,33 @@ class EnrichedTextView : AppCompatTextView {
305309
setTextSize(TypedValue.COMPLEX_UNIT_PX, sizeInt)
306310
}
307311

312+
fun setLineHeight(height: Float) {
313+
lineHeight = if (height <= 0f) null else height
314+
applyLineSpacing()
315+
}
316+
317+
private fun applyLineSpacing() {
318+
val currentText = text ?: return
319+
val spannable =
320+
currentText as? Spannable ?: SpannableString(currentText)
321+
spannable
322+
.getSpans(0, spannable.length, EnrichedLineHeightSpan::class.java)
323+
.forEach { spannable.removeSpan(it) }
324+
325+
lineHeight?.let {
326+
spannable.setSpan(
327+
EnrichedLineHeightSpan(it, allowFontScaling),
328+
0,
329+
spannable.length,
330+
Spannable.SPAN_INCLUSIVE_INCLUSIVE,
331+
)
332+
}
333+
334+
if (spannable !== currentText) {
335+
setText(spannable, BufferType.SPANNABLE)
336+
}
337+
}
338+
308339
fun setFontFamily(family: String?) {
309340
if (family != fontFamily) {
310341
fontFamily = family

android/src/main/java/com/swmansion/enriched/text/EnrichedTextViewManager.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ class EnrichedTextViewManager :
5353
view?.setFontSize(value)
5454
}
5555

56+
override fun setLineHeight(
57+
view: EnrichedTextView?,
58+
value: Float,
59+
) {
60+
view?.setLineHeight(value)
61+
}
62+
5663
override fun setFontFamily(
5764
view: EnrichedTextView?,
5865
value: String?,

android/src/main/java/com/swmansion/enriched/text/MeasurementStore.kt

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import android.content.Context
44
import android.graphics.Typeface
55
import android.graphics.text.LineBreaker
66
import android.os.Build
7+
import android.text.Spannable
8+
import android.text.SpannableString
79
import android.text.StaticLayout
810
import android.text.TextPaint
911
import android.text.TextUtils
@@ -21,6 +23,7 @@ import com.swmansion.enriched.common.GumboNormalizer
2123
import com.swmansion.enriched.common.allowFontScalingFromProps
2224
import com.swmansion.enriched.common.parser.EnrichedParser
2325
import com.swmansion.enriched.common.pixelFromSpOrDp
26+
import com.swmansion.enriched.textinput.spans.EnrichedLineHeightSpan
2427
import kotlin.math.ceil
2528

2629
object MeasurementStore {
@@ -134,6 +137,13 @@ object MeasurementStore {
134137
return props.getBoolean("useHtmlNormalizer")
135138
}
136139

140+
private fun lineHeightFromProps(props: ReadableMap?): Float {
141+
if (props == null || !props.hasKey("lineHeight") || props.isNull("lineHeight")) {
142+
return 0f
143+
}
144+
return props.getDouble("lineHeight").toFloat()
145+
}
146+
137147
private fun getInitialFontSize(props: ReadableMap?): Float {
138148
val propsFontSize = props?.getDouble("fontSize")?.toFloat() ?: EnrichedConstants.TEXT_DEFAULT_FONT_SIZE
139149
val fontSize =
@@ -151,15 +161,31 @@ object MeasurementStore {
151161
props: ReadableMap?,
152162
): Long {
153163
val fontSize = getInitialFontSize(props)
154-
val text = getInitialText(context, fontSize.toInt(), props)
164+
val rawText = getInitialText(context, fontSize.toInt(), props)
165+
val lineHeight = lineHeightFromProps(props)
166+
val allowFontScaling = allowFontScalingFromProps(props)
167+
168+
val measuredText: CharSequence =
169+
if (lineHeight > 0f) {
170+
val spannable = SpannableString(rawText)
171+
spannable.setSpan(
172+
EnrichedLineHeightSpan(lineHeight, allowFontScaling),
173+
0,
174+
spannable.length,
175+
Spannable.SPAN_INCLUSIVE_INCLUSIVE,
176+
)
177+
spannable
178+
} else {
179+
rawText
180+
}
155181

156182
val fontFamily = props?.getString("fontFamily")
157183
val numberOfLines = props?.getInt("numberOfLines") ?: 0
158184
val ellipsizeMode = props?.getString("ellipsizeMode")
159185
val fontStyle = parseFontStyle(props?.getString("fontStyle"))
160186
val fontWeight = parseFontWeight(props?.getString("fontWeight"))
161187
val typeface = applyStyles(null, fontStyle, fontWeight, fontFamily, context.assets)
162-
val size = measure(width, text, typeface, fontSize, numberOfLines, ellipsizeMode)
188+
val size = measure(width, measuredText, typeface, fontSize, numberOfLines, ellipsizeMode)
163189

164190
return size
165191
}

android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,7 @@ class EnrichedTextInputView :
568568
}
569569

570570
fun setLineHeight(height: Float) {
571-
lineHeight = if (height == 0f) null else height
571+
lineHeight = if (height <= 0f) null else height
572572
applyLineSpacing()
573573
layoutManager.invalidateLayout()
574574
forceScrollToSelection()

android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedLineHeightSpan.kt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,6 @@ class EnrichedLineHeightSpan(
2929
v: Int,
3030
fm: Paint.FontMetricsInt,
3131
) {
32-
val spannable = text as? Spannable ?: return
33-
// Do not modify line height for headings
34-
// In the future we may consider adding custom lineHeight support for each paragraph style
35-
if (spannable.getSpans(start, end, EnrichedHeadingSpan::class.java).isNotEmpty()) return
36-
3732
val lineHeightPx = pixelFromSpOrDp(lineHeight, allowFontScaling)
3833
val currentHeight = (fm.descent - fm.ascent).toFloat()
3934
if (lineHeightPx <= currentHeight) return

android/src/main/new_arch/react/renderer/components/ReactNativeEnrichedSpec/conversions.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ inline folly::dynamic toDynamic(const EnrichedTextViewProps &props) {
3434
serializedProps["fontWeight"] = props.fontWeight;
3535
serializedProps["fontStyle"] = props.fontStyle;
3636
serializedProps["fontFamily"] = props.fontFamily;
37+
serializedProps["lineHeight"] = props.lineHeight;
3738
serializedProps["numberOfLines"] = props.numberOfLines;
3839
serializedProps["ellipsizeMode"] = props.ellipsizeMode;
3940
serializedProps["allowFontScaling"] = props.allowFontScaling;

ios/EnrichedTextView.mm

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ - (void)updateProps:(Props::Shared const &)props
154154
stylePropChanged = YES;
155155
}
156156

157+
// lineHeight
158+
if (newViewProps.lineHeight != oldViewProps.lineHeight) {
159+
[newConfig setPrimaryLineHeight:newViewProps.lineHeight];
160+
stylePropChanged = YES;
161+
}
162+
157163
// fontWeight
158164
if (newViewProps.fontWeight != oldViewProps.fontWeight) {
159165
if (!newViewProps.fontWeight.empty()) {

src/spec/EnrichedTextNativeComponent.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export interface NativeProps extends ViewProps {
100100
// These should not be passed as regular props
101101
color?: ColorValue;
102102
fontSize?: Float;
103+
lineHeight?: Float;
103104
fontFamily?: string;
104105
fontWeight?: string;
105106
fontStyle?: string;

0 commit comments

Comments
 (0)