Repository navigation
fix(android): stop cutting off the last line of content-sized text - #1107
Conversation
Measure text the way the text views lay it out: by glyph bounds on API 35+, with an anti-aliased paint, and with the same default break strategy. A view sized to its content no longer comes out a pixel or two narrow and wraps its last word into a line it has no room for. Fixes #1103
There was a problem hiding this comment.
🟢 Approval recommended
No active review issues were identified for the Android text-measurement changes intended to prevent last-line clipping.
No open issues 🎉
What changed in this PR
Aligns Android text measurement with rendering to prevent the last line from being clipped in content-sized EnrichedMarkdownText views, addressing #1103 for both CommonMark and GitHub flavors.
Changes:
- Aligns the default line-break strategy and enables glyph-bound widths on API 35+.
- Enables anti-aliasing for the shared measurement paint, including after resets.
- Centralizes measurement layout construction and applies bounds-based widths to segment measurement.
| File | Description |
|---|---|
packages/react-native-enriched-markdown/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextViewSetup.kt |
Aligns view break strategy and glyph-bound sizing with measurement. |
packages/react-native-enriched-markdown/android/src/main/java/com/swmansion/enriched/markdown/segments/SegmentHeightMeasurer.kt |
Enables bounds-based widths for segment layouts. |
packages/react-native-enriched-markdown/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt |
Enables anti-aliased measurement and shares consistent layout construction. |
Review effort: Balanced
maksg
left a comment
There was a problem hiding this comment.
Approving, looks good 🎉
Some non-blocking notes:
Checked against the Android 15 sources: TextView enables useBoundsForWidth only for apps targeting 35+, the view's flag reaches its DynamicLayout, and getLineWidth is bounds-aware, so the measured width really grows with the overhang. The SIMPLE default is real too: AccessibleMarkdownTextView passes defStyleAttr = 0.
Setting the flag explicitly is the only option, by the way: StaticLayout.Builder.obtain() never resets mUseBoundsForWidth, and builders are pooled and recycled by build(), so an unset builder inherits whatever TextView left behind.
Outside this PR, same mechanism: CellTextView vs the layouts at TableContainerView.kt:473 and :491. Columns are sized to ceil(textWidth), so a cell ending in italic can wrap and lose a line. Follow-up unless you want to fold it in.
Behaviour note: apps targeting below 35 on Android 15+ now also break lines by glyph bounds, so Android screenshot baselines may move. Right call versus pinning false.
| // TextView turns bounds-based widths on only for apps targeting API 35+, while | ||
| // MeasurementStore can't see the view. Pin it so both sides measure the same way. | ||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { | ||
| useBoundsForWidth = true |
There was a problem hiding this comment.
Nit: the API 35 guard now lives here and in MeasurementStore.applyUseBoundsForWidth. One helper next to BreakStrategyUtils would keep both pins together and spare SegmentHeightMeasurer the MeasurementStore import. Optional.
There was a problem hiding this comment.
It's a nice improvement! Pushed in f7c5245
I'll work on tables in separate PR. Thanks for pointing this out! |
|
iOS was not modified in this PR so I'm skipping its unit tests on this PR. |
What/Why?
Fixes #1103.
On Android,
EnrichedMarkdownTextsized to its content (e.g. a chat bubble) could come out a pixel or two narrower than its text, so the last word wrapped into a line the view had no room for and was cut off. Measurement and drawing laid the text out differently:useBoundsForWidth), so a line ending in italic is wider than its advances. The measuringStaticLayoutdidn't, in both the commonmark and the github path.ANTI_ALIAS_FLAG, which changes glyph widths by a few pixels.textBreakStrategyprop, the view broke lines withSIMPLEwhile measurement usedHIGH_QUALITY.The views now pin
useBoundsForWidthand start from the same break strategy, and every measuring layout uses bounds and an anti-aliased paint.Testing
The Playground preview fills the screen width, so render the text in a content-sized container instead. Before the fix, the final " it" is missing at several font sizes.
Check both
flavor="github"andflavor="commonmark".githubcommonmarkPR Checklist