fix(android): unify line break strategy across measuring and drawing, add line break props#374
fix(android): unify line break strategy across measuring and drawing, add line break props#374eszlamczyk wants to merge 5 commits into
Conversation
hryhoriiK97
left a comment
There was a problem hiding this comment.
Looks like this approach fixes the current problem, but changing from HIGH_QUALITY to SIMPLE could be a breaking change for existing library consumers. I'm thinking about introducing a lineBreakStrategy prop — the same as RN Text has: https://reactnative.dev/docs/text#textbreakstrategy-android
There was a problem hiding this comment.
Pull request overview
This PR adds cross-platform line-breaking configuration props to the library’s Markdown text components and updates native iOS/Android rendering/measurement to keep line breaking consistent (notably addressing Android height mis-measurement/clipping for long wrapped URLs/links).
Changes:
- Add
textBreakStrategy(Android) andlineBreakStrategyIOS(iOS) props to the public TS props and native codegen props forEnrichedMarkdownText/EnrichedMarkdown. - iOS: apply
lineBreakStrategyIOSviaNSParagraphStyleduring attributed-string building. - Android: centralize break-strategy resolution and apply it in both
StaticLayout.Builder(measurement) andTextView.breakStrategy(render).
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/types/MarkdownTextProps.ts | Exposes new break-strategy props on EnrichedMarkdownTextProps. |
| src/native/EnrichedMarkdownText.tsx | Wires new props from JS wrapper into native component props. |
| src/EnrichedMarkdownTextNativeComponent.ts | Adds codegen native prop definitions for EnrichedMarkdownText. |
| src/EnrichedMarkdownNativeComponent.ts | Adds codegen native prop definitions for EnrichedMarkdown (GFM/container renderer). |
| ios/utils/ParagraphStyleUtils.m | Introduces global line-break strategy and applies it to paragraph styles. |
| ios/utils/ParagraphStyleUtils.h | Declares the new line-break strategy setter. |
| ios/EnrichedMarkdownText.mm | Plumbs lineBreakStrategyIOS prop changes into the iOS text renderer. |
| ios/EnrichedMarkdown.mm | Plumbs lineBreakStrategyIOS prop changes into the iOS GFM/container renderer. |
| android/.../TextViewSetup.kt | Applies resolved break strategy to the rendered TextView on API 23+. |
| android/.../utils/common/BreakStrategyUtils.kt | Adds a shared resolver for Android break-strategy selection. |
| android/.../MeasurementStore.kt | Applies the resolved break strategy during StaticLayout measurement on API 23+. |
| android/.../EnrichedMarkdownTextManager.kt | Exposes textBreakStrategy as a React prop (and adds iOS-only prop no-ops). |
| android/.../EnrichedMarkdownText.kt | Implements setTextBreakStrategy and invalidates measurement/re-render. |
| android/.../EnrichedMarkdownManager.kt | Exposes textBreakStrategy as a React prop (and adds iOS-only prop no-ops). |
| android/.../EnrichedMarkdown.kt | Propagates break-strategy changes to segment TextViews and forces height recalculation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
| * The suppression is safe; the Layout.* constants share the same integer values. | ||
| */ | ||
| object BreakStrategyUtils { | ||
| private var strategy: String = "simple" |
There was a problem hiding this comment.
The default/fallback here is still "simple", but managers, codegen, and the JS wrapper all default to "highQuality". Let's use 'highQuality' as default.
|
|
||
| fun setStrategy(newStrategy: String?) { | ||
| strategy = newStrategy ?: "simple" | ||
| } |
There was a problem hiding this comment.
BreakStrategyUtils is a process-wide singleton, but textBreakStrategy is a per-view prop - with multiple markdown views on screen, the last setter wins for all measurement/render paths.
RN Text doesn't do this: each shadow node stores its own strategy (mTextBreakStrategy / ParagraphAttributes.textBreakStrategy) and passes the same value to both StaticLayout.Builder (measure) and ReactTextView.setBreakStrategy() (render).
Could we follow the existing MeasurementStore.fontScalingSettings pattern instead - store per viewId, resolve during measurement, set on each view's TextView?
| static NSLineBreakStrategy gLineBreakStrategy = NSLineBreakStrategyNone; | ||
|
|
||
| void ENRMSetLineBreakStrategy(NSString *strategy) | ||
| { |
There was a problem hiding this comment.
This file already has statics like kBlockSpacerTemplate, but those are immutable shared templates (copied per use, same for all views).
gLineBreakStrategy is different — it's mutable state for a per-view prop. If two markdown views use different lineBreakStrategyIOS values, the last setter wins in getOrCreateParagraphStyle.
Font scaling is already stored per view here; RN Text does the same for lineBreakStrategy. Could we store this on the view/renderer and pass it through instead of a global?
| * @default 'none' | ||
| * @platform ios | ||
| */ | ||
| lineBreakStrategyIOS?: 'none' | 'standard' | 'hangul-word' | 'push-out'; |
There was a problem hiding this comment.
Could we update API_RENCE doc with these props? 🙏
What/Why?
Adds
textBreakStrategy(Android) andlineBreakStrategyIOS(iOS) props to bothEnrichedMarkdownText, mirroring the same props available on React Native's coreTextcomponent.On Android, the break strategy must match between the measurement pass (
StaticLayout.Builder) and the render pass (TextView.breakStrategy) - a mismatch causes the measured line count to differ from the rendered one, which manifests as incorrect view height and clipped or scrolled content, most visibly with long links that wrap across lines. Previously the strategy was hardcoded tosimplevia a top-level function; this PR promotes it to a singleton (BreakStrategyUtils) that both paths read from, so changing the prop keeps them in sync.On iOS, the
lineBreakStrategyis applied to theNSParagraphStyleused when building attributed strings, allowing consumers to opt intostandard,hangul-word, orpush-outbreaking on iOS 14+.
Note: the defaults were kept in a way not to have any breaking changes
Testing
Go to playground in example app
paste (via set markdown option) string containing long text/links for example:
To make this more visible adjust max width of the container (to for example 220)
Alternative Test
Create any view that has:
<Text><Text>Example
Replace
App.tsxin example app withScreenshots
Main test
Android
simplehighQuality(default)balancediOS
none(default)standardhangul-wordpush-outAlternative test
Note: Before photo is partialy scrolled to display the issue
PR Checklist
closes #366