Skip to content

Android: editor lineHeight broken — render mixes dp/px AND auto-grow measurement ignores it #615

Description

@nick87kelly

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.textSizefontSize(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

  1. Render EnrichedMarkdownTextInput on Android with a base style like { fontSize: 16, lineHeight: 24 }.
  2. Type several lines separated by Enter.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions