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

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions