diff --git a/android/src/main/java/com/swmansion/enriched/text/EnrichedTextView.kt b/android/src/main/java/com/swmansion/enriched/text/EnrichedTextView.kt index 53a0a503a..357016da3 100644 --- a/android/src/main/java/com/swmansion/enriched/text/EnrichedTextView.kt +++ b/android/src/main/java/com/swmansion/enriched/text/EnrichedTextView.kt @@ -30,6 +30,7 @@ import com.swmansion.enriched.common.pixelFromSpOrDp import com.swmansion.enriched.text.spans.EnrichedTextImageSpan import com.swmansion.enriched.text.spans.interfaces.EnrichedTextClickableSpan import com.swmansion.enriched.text.spans.interfaces.EnrichedTextSpan +import com.swmansion.enriched.textinput.spans.EnrichedLineHeightSpan import kotlin.math.ceil class EnrichedTextView : AppCompatTextView { @@ -41,6 +42,7 @@ class EnrichedTextView : AppCompatTextView { private var fontWeight: Int = ReactConstants.UNSET private var fontSize: Float = EnrichedConstants.TEXT_DEFAULT_FONT_SIZE private var fontSizeRaw: Float? = null + private var lineHeight: Float? = null private var htmlStyleMap: ReadableMap? = null var allowFontScaling: Boolean = EnrichedConstants.ALLOW_FONT_SCALING_DEFAULT set(value) { @@ -48,6 +50,7 @@ class EnrichedTextView : AppCompatTextView { field = value fontSizeRaw?.let { setFontSize(it) } htmlStyleMap?.let { setHtmlStyle(it) } + applyLineSpacing() } private var enrichedStyle: EnrichedTextStyle? = null @@ -185,6 +188,7 @@ class EnrichedTextView : AppCompatTextView { parsedText = null this.text = text } + applyLineSpacing() } private fun parseText( @@ -305,6 +309,33 @@ class EnrichedTextView : AppCompatTextView { setTextSize(TypedValue.COMPLEX_UNIT_PX, sizeInt) } + fun setLineHeight(height: Float) { + lineHeight = if (height <= 0f) null else height + applyLineSpacing() + } + + private fun applyLineSpacing() { + val currentText = text ?: return + val spannable = + currentText as? Spannable ?: SpannableString(currentText) + spannable + .getSpans(0, spannable.length, EnrichedLineHeightSpan::class.java) + .forEach { spannable.removeSpan(it) } + + lineHeight?.let { + spannable.setSpan( + EnrichedLineHeightSpan(it, allowFontScaling), + 0, + spannable.length, + Spannable.SPAN_INCLUSIVE_INCLUSIVE, + ) + } + + if (spannable !== currentText) { + setText(spannable, BufferType.SPANNABLE) + } + } + fun setFontFamily(family: String?) { if (family != fontFamily) { fontFamily = family diff --git a/android/src/main/java/com/swmansion/enriched/text/EnrichedTextViewManager.kt b/android/src/main/java/com/swmansion/enriched/text/EnrichedTextViewManager.kt index 62f91256c..cfce40f0c 100644 --- a/android/src/main/java/com/swmansion/enriched/text/EnrichedTextViewManager.kt +++ b/android/src/main/java/com/swmansion/enriched/text/EnrichedTextViewManager.kt @@ -53,6 +53,13 @@ class EnrichedTextViewManager : view?.setFontSize(value) } + override fun setLineHeight( + view: EnrichedTextView?, + value: Float, + ) { + view?.setLineHeight(value) + } + override fun setFontFamily( view: EnrichedTextView?, value: String?, diff --git a/android/src/main/java/com/swmansion/enriched/text/MeasurementStore.kt b/android/src/main/java/com/swmansion/enriched/text/MeasurementStore.kt index b82c75212..f2805659c 100644 --- a/android/src/main/java/com/swmansion/enriched/text/MeasurementStore.kt +++ b/android/src/main/java/com/swmansion/enriched/text/MeasurementStore.kt @@ -4,6 +4,8 @@ import android.content.Context import android.graphics.Typeface import android.graphics.text.LineBreaker import android.os.Build +import android.text.Spannable +import android.text.SpannableString import android.text.StaticLayout import android.text.TextPaint import android.text.TextUtils @@ -21,6 +23,7 @@ import com.swmansion.enriched.common.GumboNormalizer import com.swmansion.enriched.common.allowFontScalingFromProps import com.swmansion.enriched.common.parser.EnrichedParser import com.swmansion.enriched.common.pixelFromSpOrDp +import com.swmansion.enriched.textinput.spans.EnrichedLineHeightSpan import kotlin.math.ceil object MeasurementStore { @@ -134,6 +137,13 @@ object MeasurementStore { return props.getBoolean("useHtmlNormalizer") } + private fun lineHeightFromProps(props: ReadableMap?): Float { + if (props == null || !props.hasKey("lineHeight") || props.isNull("lineHeight")) { + return 0f + } + return props.getDouble("lineHeight").toFloat() + } + private fun getInitialFontSize(props: ReadableMap?): Float { val propsFontSize = props?.getDouble("fontSize")?.toFloat() ?: EnrichedConstants.TEXT_DEFAULT_FONT_SIZE val fontSize = @@ -151,7 +161,23 @@ object MeasurementStore { props: ReadableMap?, ): Long { val fontSize = getInitialFontSize(props) - val text = getInitialText(context, fontSize.toInt(), props) + val rawText = getInitialText(context, fontSize.toInt(), props) + val lineHeight = lineHeightFromProps(props) + val allowFontScaling = allowFontScalingFromProps(props) + + val measuredText: CharSequence = + if (lineHeight > 0f) { + val spannable = SpannableString(rawText) + spannable.setSpan( + EnrichedLineHeightSpan(lineHeight, allowFontScaling), + 0, + spannable.length, + Spannable.SPAN_INCLUSIVE_INCLUSIVE, + ) + spannable + } else { + rawText + } val fontFamily = props?.getString("fontFamily") val numberOfLines = props?.getInt("numberOfLines") ?: 0 @@ -159,7 +185,7 @@ object MeasurementStore { val fontStyle = parseFontStyle(props?.getString("fontStyle")) val fontWeight = parseFontWeight(props?.getString("fontWeight")) val typeface = applyStyles(null, fontStyle, fontWeight, fontFamily, context.assets) - val size = measure(width, text, typeface, fontSize, numberOfLines, ellipsizeMode) + val size = measure(width, measuredText, typeface, fontSize, numberOfLines, ellipsizeMode) return size } diff --git a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt index 023cc20fc..f4aed71ea 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt @@ -568,7 +568,7 @@ class EnrichedTextInputView : } fun setLineHeight(height: Float) { - lineHeight = if (height == 0f) null else height + lineHeight = if (height <= 0f) null else height applyLineSpacing() layoutManager.invalidateLayout() forceScrollToSelection() diff --git a/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedLineHeightSpan.kt b/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedLineHeightSpan.kt index ddaeb7a5d..dea82506f 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedLineHeightSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedLineHeightSpan.kt @@ -29,11 +29,6 @@ class EnrichedLineHeightSpan( v: Int, fm: Paint.FontMetricsInt, ) { - val spannable = text as? Spannable ?: return - // Do not modify line height for headings - // In the future we may consider adding custom lineHeight support for each paragraph style - if (spannable.getSpans(start, end, EnrichedHeadingSpan::class.java).isNotEmpty()) return - val lineHeightPx = pixelFromSpOrDp(lineHeight, allowFontScaling) val currentHeight = (fm.descent - fm.ascent).toFloat() if (lineHeightPx <= currentHeight) return diff --git a/android/src/main/new_arch/react/renderer/components/ReactNativeEnrichedSpec/conversions.h b/android/src/main/new_arch/react/renderer/components/ReactNativeEnrichedSpec/conversions.h index 195e70643..782e3895b 100644 --- a/android/src/main/new_arch/react/renderer/components/ReactNativeEnrichedSpec/conversions.h +++ b/android/src/main/new_arch/react/renderer/components/ReactNativeEnrichedSpec/conversions.h @@ -34,6 +34,7 @@ inline folly::dynamic toDynamic(const EnrichedTextViewProps &props) { serializedProps["fontWeight"] = props.fontWeight; serializedProps["fontStyle"] = props.fontStyle; serializedProps["fontFamily"] = props.fontFamily; + serializedProps["lineHeight"] = props.lineHeight; serializedProps["numberOfLines"] = props.numberOfLines; serializedProps["ellipsizeMode"] = props.ellipsizeMode; serializedProps["allowFontScaling"] = props.allowFontScaling; diff --git a/ios/EnrichedTextView.mm b/ios/EnrichedTextView.mm index 16ef47083..2396367cc 100644 --- a/ios/EnrichedTextView.mm +++ b/ios/EnrichedTextView.mm @@ -154,6 +154,12 @@ - (void)updateProps:(Props::Shared const &)props stylePropChanged = YES; } + // lineHeight + if (newViewProps.lineHeight != oldViewProps.lineHeight) { + [newConfig setPrimaryLineHeight:newViewProps.lineHeight]; + stylePropChanged = YES; + } + // fontWeight if (newViewProps.fontWeight != oldViewProps.fontWeight) { if (!newViewProps.fontWeight.empty()) { diff --git a/src/spec/EnrichedTextNativeComponent.ts b/src/spec/EnrichedTextNativeComponent.ts index fec62bd3d..f25ff8a52 100644 --- a/src/spec/EnrichedTextNativeComponent.ts +++ b/src/spec/EnrichedTextNativeComponent.ts @@ -100,6 +100,7 @@ export interface NativeProps extends ViewProps { // These should not be passed as regular props color?: ColorValue; fontSize?: Float; + lineHeight?: Float; fontFamily?: string; fontWeight?: string; fontStyle?: string;