Describe the bug
On Android, supplying a lineHeight to the editor's style is broken in two coupled ways. Together they make multi-line editing unusable when lineHeight is set — the same "line-height collapses / text is compressed" symptom from #359 (closed once the lineHeight prop was added), plus an auto-grow problem. iOS is unaffected in both cases.
There are two separate code paths for line height on Android — the render path and the measurement (auto-grow) path — and they disagree.
Bug 1 — render: setLineHeight mixes dp and px
EnrichedMarkdownTextInputManager.setLineHeight computes the extra spacing as value - view.textSize:
// android/.../input/EnrichedMarkdownTextInputManager.kt
@ReactProp(name = "lineHeight", defaultFloat = 0f)
override fun setLineHeight(view: EnrichedMarkdownTextInputView?, value: Float) {
if (value > 0 && view != null) {
view.setLineSpacing(value - view.textSize, 1f) // unit mismatch
}
}
value (the RN lineHeight) arrives as a raw dp number — @ReactProp floats are not density-converted. But setLineSpacing(add, mult) and view.textSize are both in px. So:
lineHeight = 24 (dp) → passed as 24f
view.textSize ≈ fontSize(16) × density(2.75) ≈ 44px
setLineSpacing(24 − 44, 1f) → setLineSpacing(-20px, 1f) → negative spacing → lines overlap.
Fix: convert dp → px before subtracting:
import com.facebook.react.uimanager.PixelUtil
view.setLineSpacing(PixelUtil.toPixelFromDIP(value) - view.textSize, 1f)
Bug 2 — measure: auto-grow ignores lineHeight, so the input grows too slowly
Once bug 1 is fixed (render now adds correct positive spacing), a second bug surfaces: the height the editor reports for auto-grow is measured without that spacing, so the input grows slower than its content and the content scrolls internally instead of expanding.
The measurement path builds its StaticLayout with a hardcoded setLineSpacing(0f, 1f):
// android/.../input/layout/InputMeasurementStore.kt (measure())
StaticLayout.Builder
.obtain(content, 0, content.length, paint, widthPx)
.setIncludePad(true)
.setLineSpacing(0f, 1f) // ignores lineHeight entirely
So the reported height counts each line without the extra spacing the view actually renders. Per line it falls short by (lineHeight − textSize), and the shortfall accumulates as lines are added.
Fix: measure with the same spacing the view renders. The cleanest way to guarantee the two paths never diverge is to use the view as the single source of truth — InputLayoutManager already has the view when it triggers a re-measure, so pass view.lineSpacingExtra / view.lineSpacingMultiplier into InputMeasurementStore.store() and use them in measure() instead of the hardcoded (0f, 1f). For the first-pass initialMeasure (before the view exists) derive the spacing from props using the same PixelUtil.toPixelFromDIP(lineHeight) - textSize formula as setLineHeight.
To Reproduce
- Render
EnrichedMarkdownTextInput on Android with a base style like { fontSize: 16, lineHeight: 24 }.
- Type several lines separated by Enter.
- Before bug 1 is fixed: lines overlap. After fixing only bug 1: spacing is correct but the input grows too slowly and its content becomes internally scrollable. iOS renders correctly in both cases.
Expected behavior
lineHeight: 24 with fontSize: 16 on Android should render non-overlapping lines and auto-grow at the correct rate, matching iOS and matching the read-only renderer.
Environment
react-native-enriched-markdown 0.7.4
react-native 0.81.5, Expo SDK 54
- Platform: Android (iOS unaffected)
Happy to open a PR covering both — render (dp→px) plus the view-sourced measurement.
Describe the bug
On Android, supplying a
lineHeightto the editor'sstyleis broken in two coupled ways. Together they make multi-line editing unusable whenlineHeightis set — the same "line-height collapses / text is compressed" symptom from #359 (closed once thelineHeightprop was added), plus an auto-grow problem. iOS is unaffected in both cases.There are two separate code paths for line height on Android — the render path and the measurement (auto-grow) path — and they disagree.
Bug 1 — render:
setLineHeightmixes dp and pxEnrichedMarkdownTextInputManager.setLineHeightcomputes the extra spacing asvalue - view.textSize:value(the RNlineHeight) arrives as a raw dp number —@ReactPropfloats are not density-converted. ButsetLineSpacing(add, mult)andview.textSizeare both in px. So:lineHeight = 24(dp) → passed as24fview.textSize≈fontSize(16) × density(2.75)≈44pxsetLineSpacing(24 − 44, 1f)→setLineSpacing(-20px, 1f)→ negative spacing → lines overlap.Fix: convert dp → px before subtracting:
Bug 2 — measure: auto-grow ignores
lineHeight, so the input grows too slowlyOnce bug 1 is fixed (render now adds correct positive spacing), a second bug surfaces: the height the editor reports for auto-grow is measured without that spacing, so the input grows slower than its content and the content scrolls internally instead of expanding.
The measurement path builds its
StaticLayoutwith a hardcodedsetLineSpacing(0f, 1f):So the reported height counts each line without the extra spacing the view actually renders. Per line it falls short by
(lineHeight − textSize), and the shortfall accumulates as lines are added.Fix: measure with the same spacing the view renders. The cleanest way to guarantee the two paths never diverge is to use the view as the single source of truth —
InputLayoutManageralready has the view when it triggers a re-measure, so passview.lineSpacingExtra/view.lineSpacingMultiplierintoInputMeasurementStore.store()and use them inmeasure()instead of the hardcoded(0f, 1f). For the first-passinitialMeasure(before the view exists) derive the spacing from props using the samePixelUtil.toPixelFromDIP(lineHeight) - textSizeformula assetLineHeight.To Reproduce
EnrichedMarkdownTextInputon Android with a basestylelike{ fontSize: 16, lineHeight: 24 }.Expected behavior
lineHeight: 24withfontSize: 16on Android should render non-overlapping lines and auto-grow at the correct rate, matching iOS and matching the read-only renderer.Environment
react-native-enriched-markdown0.7.4react-native0.81.5, Expo SDK 54Happy to open a PR covering both — render (dp→px) plus the view-sourced measurement.