From 4c1650095a7e7c2ca35383b0b5bc2fa31ffb982c Mon Sep 17 00:00:00 2001 From: Tomek Zawadzki Date: Tue, 5 Nov 2024 17:49:24 +0100 Subject: [PATCH 01/15] Customize line height for headings --- .../com/expensify/livemarkdown/MarkdownStyle.java | 7 +++++++ .../com/expensify/livemarkdown/MarkdownUtils.java | 8 ++++---- .../livemarkdown/spans/MarkdownLineHeightSpan.java | 11 +++++++---- apple/RCTMarkdownStyle.h | 1 + apple/RCTMarkdownStyle.mm | 2 ++ apple/RCTMarkdownUtils.mm | 5 +++++ example/ios/Podfile.lock | 8 ++++---- example/src/App.tsx | 3 +++ src/MarkdownTextInputDecoratorViewNativeComponent.ts | 1 + src/styleUtils.ts | 1 + 10 files changed, 35 insertions(+), 12 deletions(-) diff --git a/android/src/main/java/com/expensify/livemarkdown/MarkdownStyle.java b/android/src/main/java/com/expensify/livemarkdown/MarkdownStyle.java index cf691e62..88bbbf1b 100644 --- a/android/src/main/java/com/expensify/livemarkdown/MarkdownStyle.java +++ b/android/src/main/java/com/expensify/livemarkdown/MarkdownStyle.java @@ -21,6 +21,8 @@ public class MarkdownStyle { private final float mH1FontSize; + private final float mH1LineHeight; + private final float mEmojiFontSize; @ColorInt @@ -74,6 +76,7 @@ public MarkdownStyle(@NonNull ReadableMap map, @NonNull Context context) { mSyntaxColor = parseColor(map, "syntax", "color", context); mLinkColor = parseColor(map, "link", "color", context); mH1FontSize = parseFloat(map, "h1", "fontSize"); + mH1LineHeight = parseFloat(map, "h1", "lineHeight"); mEmojiFontSize = parseFloat(map, "emoji", "fontSize"); mBlockquoteBorderColor = parseColor(map, "blockquote", "borderColor", context); mBlockquoteBorderWidth = parseFloat(map, "blockquote", "borderWidth"); @@ -136,6 +139,10 @@ public float getH1FontSize() { return mH1FontSize; } + public float getH1LineHeight() { + return mH1LineHeight; + } + public float getEmojiFontSize() { return mEmojiFontSize; } diff --git a/android/src/main/java/com/expensify/livemarkdown/MarkdownUtils.java b/android/src/main/java/com/expensify/livemarkdown/MarkdownUtils.java index e51aa4b4..e95c131a 100644 --- a/android/src/main/java/com/expensify/livemarkdown/MarkdownUtils.java +++ b/android/src/main/java/com/expensify/livemarkdown/MarkdownUtils.java @@ -148,10 +148,10 @@ private void applyRange(SpannableStringBuilder ssb, String type, int start, int break; case "h1": setSpan(ssb, new MarkdownBoldSpan(), start, end); - CustomLineHeightSpan[] spans = ssb.getSpans(0, ssb.length(), CustomLineHeightSpan.class); - if (spans.length >= 1) { - int lineHeight = spans[0].getLineHeight(); - setSpan(ssb, new MarkdownLineHeightSpan(lineHeight * 1.5f), start, end); + float lineHeight = mMarkdownStyle.getH1LineHeight(); + if (lineHeight != -1) { + // NOTE: actually, we should also include "# " but it also works this way + setSpan(ssb, new MarkdownLineHeightSpan(lineHeight), start, end); } // NOTE: size span must be set after line height span to avoid height jumps setSpan(ssb, new MarkdownFontSizeSpan(mMarkdownStyle.getH1FontSize()), start, end); diff --git a/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownLineHeightSpan.java b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownLineHeightSpan.java index 3f4c33fc..936020e7 100644 --- a/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownLineHeightSpan.java +++ b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownLineHeightSpan.java @@ -3,16 +3,19 @@ import android.graphics.Paint; import android.text.style.LineHeightSpan; +import com.facebook.react.uimanager.PixelUtil; +import com.facebook.react.views.text.internal.span.CustomLineHeightSpan; + public class MarkdownLineHeightSpan implements MarkdownSpan, LineHeightSpan { - private final float mLineHeight; + private final CustomLineHeightSpan mCustomLineHeightSpan; public MarkdownLineHeightSpan(float lineHeight) { - mLineHeight = lineHeight; + mCustomLineHeightSpan = new CustomLineHeightSpan(PixelUtil.toPixelFromDIP(lineHeight)); } @Override public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int lineHeight, Paint.FontMetricsInt fm) { - fm.top -= mLineHeight / 4; - fm.ascent -= mLineHeight / 4; + // CustomLineHeightSpan is marked as final, we can't extend it, but we can use it via this adapter + mCustomLineHeightSpan.chooseHeight(text, start, end, spanstartv, lineHeight, fm); } } diff --git a/apple/RCTMarkdownStyle.h b/apple/RCTMarkdownStyle.h index 75a27f16..0c1c1edc 100644 --- a/apple/RCTMarkdownStyle.h +++ b/apple/RCTMarkdownStyle.h @@ -11,6 +11,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) UIColor *syntaxColor; @property (nonatomic) UIColor *linkColor; @property (nonatomic) CGFloat h1FontSize; +@property (nonatomic) CGFloat h1LineHeight; @property (nonatomic) CGFloat emojiFontSize; @property (nonatomic) UIColor *blockquoteBorderColor; @property (nonatomic) CGFloat blockquoteBorderWidth; diff --git a/apple/RCTMarkdownStyle.mm b/apple/RCTMarkdownStyle.mm index a2752cdf..1a031a0e 100644 --- a/apple/RCTMarkdownStyle.mm +++ b/apple/RCTMarkdownStyle.mm @@ -18,6 +18,7 @@ - (instancetype)initWithStruct:(const facebook::react::MarkdownTextInputDecorato _linkColor = RCTUIColorFromSharedColor(style.link.color); _h1FontSize = style.h1.fontSize; + _h1LineHeight = style.h1.lineHeight; _emojiFontSize = style.emoji.fontSize; @@ -59,6 +60,7 @@ - (instancetype)initWithDictionary:(NSDictionary *)json _linkColor = [RCTConvert UIColor:json[@"link"][@"color"]]; _h1FontSize = [RCTConvert CGFloat:json[@"h1"][@"fontSize"]]; + _h1LineHeight = [RCTConvert CGFloat:json[@"h1"][@"lineHeight"]]; _emojiFontSize = [RCTConvert CGFloat:json[@"emoji"][@"fontSize"]]; diff --git a/apple/RCTMarkdownUtils.mm b/apple/RCTMarkdownUtils.mm index c22a8784..ff7849a5 100644 --- a/apple/RCTMarkdownUtils.mm +++ b/apple/RCTMarkdownUtils.mm @@ -159,6 +159,11 @@ - (void)applyRangeToAttributedString:(NSMutableAttributedString *)attributedStri @"range": [NSValue valueWithRange:range], @"depth": @(depth) }]; + } else if (type == "h1" && _markdownStyle.h1LineHeight != -1) { + NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new]; + paragraphStyle.lineHeightMultiple = _markdownStyle.h1LineHeight / _markdownStyle.h1FontSize; + NSRange rangeWithHashAndSpace = NSMakeRange(range.location - 2, range.length + 2); + [attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:rangeWithHashAndSpace]; } else if (type == "pre") { [attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.preColor range:range]; NSRange rangeForBackground = [[attributedString string] characterAtIndex:range.location] == '\n' ? NSMakeRange(range.location + 1, range.length - 1) : range; diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 14c774cb..df65eab1 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1497,7 +1497,7 @@ PODS: - React-logger (= 0.75.2) - React-perflogger (= 0.75.2) - React-utils (= 0.75.2) - - RNLiveMarkdown (0.1.169): + - RNLiveMarkdown (0.1.180): - DoubleConversion - glog - hermes-engine @@ -1517,9 +1517,9 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNLiveMarkdown/newarch (= 0.1.169) + - RNLiveMarkdown/newarch (= 0.1.180) - Yoga - - RNLiveMarkdown/newarch (0.1.169): + - RNLiveMarkdown/newarch (0.1.180): - DoubleConversion - glog - hermes-engine @@ -1805,7 +1805,7 @@ SPEC CHECKSUMS: React-utils: 81a715d9c0a2a49047e77a86f3a2247408540deb ReactCodegen: 60973d382704c793c605b9be0fc7f31cb279442f ReactCommon: 6ef348087d250257c44c0204461c03f036650e9b - RNLiveMarkdown: 00ab78496be2ae15a15a83f14ba732c01624f02c + RNLiveMarkdown: fc07b203a3ed832e2e5d3950e69cd4fc3b0568b6 SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d Yoga: 2a45d7e59592db061217551fd3bbe2dd993817ae diff --git a/example/src/App.tsx b/example/src/App.tsx index 235ff535..13c17675 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -27,6 +27,9 @@ export default function App() { link: { color: linkColorState ? 'red' : 'blue', }, + h1: { + lineHeight: 50, + }, }; }, [emojiFontSizeState, linkColorState]); diff --git a/src/MarkdownTextInputDecoratorViewNativeComponent.ts b/src/MarkdownTextInputDecoratorViewNativeComponent.ts index 7617963d..14fa57ac 100644 --- a/src/MarkdownTextInputDecoratorViewNativeComponent.ts +++ b/src/MarkdownTextInputDecoratorViewNativeComponent.ts @@ -15,6 +15,7 @@ interface MarkdownStyle { }; h1: { fontSize: Float; + lineHeight: Float; }; blockquote: { borderColor: ColorValue; diff --git a/src/styleUtils.ts b/src/styleUtils.ts index 47cfa529..bb2e6343 100644 --- a/src/styleUtils.ts +++ b/src/styleUtils.ts @@ -20,6 +20,7 @@ function makeDefaultMarkdownStyle(): MarkdownStyle { }, h1: { fontSize: 25, + lineHeight: -1, }, emoji: { fontSize: 20, From 29b8bc9494424a004b7f804974831d8e60540989 Mon Sep 17 00:00:00 2001 From: Tomek Zawadzki Date: Tue, 5 Nov 2024 18:00:43 +0100 Subject: [PATCH 02/15] Fix web tests --- WebExample/__tests__/styles.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebExample/__tests__/styles.spec.ts b/WebExample/__tests__/styles.spec.ts index f077423d..a5c36f38 100644 --- a/WebExample/__tests__/styles.spec.ts +++ b/WebExample/__tests__/styles.spec.ts @@ -27,7 +27,7 @@ test.describe('markdown content styling', () => { }); test('h1', async ({page}) => { - await testMarkdownContentStyle({testContent: 'header1', style: 'font-size: 25px; font-weight: bold;', page}); + await testMarkdownContentStyle({testContent: 'header1', style: 'font-size: 25px; line-height: 50px; font-weight: bold;', page}); }); test('inline code', async ({page}) => { From 30ab8bd53c8a6752a73a4c456817d694c12e10ed Mon Sep 17 00:00:00 2001 From: Tomek Zawadzki Date: Wed, 6 Nov 2024 07:25:40 +0100 Subject: [PATCH 03/15] Update Podfile.lock --- example/ios/Podfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index df65eab1..8caf66ff 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1497,7 +1497,7 @@ PODS: - React-logger (= 0.75.2) - React-perflogger (= 0.75.2) - React-utils (= 0.75.2) - - RNLiveMarkdown (0.1.180): + - RNLiveMarkdown (0.1.181): - DoubleConversion - glog - hermes-engine @@ -1517,9 +1517,9 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNLiveMarkdown/newarch (= 0.1.180) + - RNLiveMarkdown/newarch (= 0.1.181) - Yoga - - RNLiveMarkdown/newarch (0.1.180): + - RNLiveMarkdown/newarch (0.1.181): - DoubleConversion - glog - hermes-engine @@ -1805,7 +1805,7 @@ SPEC CHECKSUMS: React-utils: 81a715d9c0a2a49047e77a86f3a2247408540deb ReactCodegen: 60973d382704c793c605b9be0fc7f31cb279442f ReactCommon: 6ef348087d250257c44c0204461c03f036650e9b - RNLiveMarkdown: fc07b203a3ed832e2e5d3950e69cd4fc3b0568b6 + RNLiveMarkdown: e4b2afcbade12605178d367b64eaa775df6c21dd SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d Yoga: 2a45d7e59592db061217551fd3bbe2dd993817ae From 0c0a6dc337b11f11cdfc76e040f4f5c751eafe03 Mon Sep 17 00:00:00 2001 From: Tomek Zawadzki Date: Mon, 9 Dec 2024 11:24:50 +0100 Subject: [PATCH 04/15] Update Podfile.lock --- example/ios/Podfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index f3bb3529..aba22425 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1497,7 +1497,7 @@ PODS: - React-logger (= 0.75.3) - React-perflogger (= 0.75.3) - React-utils (= 0.75.3) - - RNLiveMarkdown (0.1.199): + - RNLiveMarkdown (0.1.203): - DoubleConversion - glog - hermes-engine @@ -1517,10 +1517,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNLiveMarkdown/newarch (= 0.1.199) + - RNLiveMarkdown/newarch (= 0.1.203) - RNReanimated/worklets - Yoga - - RNLiveMarkdown/newarch (0.1.199): + - RNLiveMarkdown/newarch (0.1.203): - DoubleConversion - glog - hermes-engine @@ -1897,7 +1897,7 @@ SPEC CHECKSUMS: React-utils: f2afa6acd905ca2ce7bb8ffb4a22f7f8a12534e8 ReactCodegen: e35c23cdd36922f6d2990c6c1f1b022ade7ad74d ReactCommon: 289214026502e6a93484f4a46bcc0efa4f3f2864 - RNLiveMarkdown: 18dd4ceada29d66a6b7c29b1b0df589e2fc82183 + RNLiveMarkdown: ed779eaf35a346f5254079b825a13f0a032ba64a RNReanimated: ab6c33a61e90c4cbe5dbcbe65bd6c7cb3be167e6 SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d Yoga: 1354c027ab07c7736f99a3bef16172d6f1b12b47 From a08da3becf1d9323ab38890d03a7fbfced12f03c Mon Sep 17 00:00:00 2001 From: Tomek Zawadzki Date: Sun, 22 Dec 2024 10:12:35 +0100 Subject: [PATCH 05/15] Update Podfile.lock --- example/ios/Podfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index a8d14f2a..f8c3e032 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1497,7 +1497,7 @@ PODS: - React-logger (= 0.75.3) - React-perflogger (= 0.75.3) - React-utils (= 0.75.3) - - RNLiveMarkdown (0.1.210): + - RNLiveMarkdown (0.1.211): - DoubleConversion - glog - hermes-engine @@ -1517,10 +1517,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNLiveMarkdown/newarch (= 0.1.210) + - RNLiveMarkdown/newarch (= 0.1.211) - RNReanimated/worklets - Yoga - - RNLiveMarkdown/newarch (0.1.210): + - RNLiveMarkdown/newarch (0.1.211): - DoubleConversion - glog - hermes-engine @@ -1897,7 +1897,7 @@ SPEC CHECKSUMS: React-utils: f2afa6acd905ca2ce7bb8ffb4a22f7f8a12534e8 ReactCodegen: e35c23cdd36922f6d2990c6c1f1b022ade7ad74d ReactCommon: 289214026502e6a93484f4a46bcc0efa4f3f2864 - RNLiveMarkdown: 687bc45ffb3b4af261f414fea169f10eae5ac261 + RNLiveMarkdown: d14eeb66f85495e6d42829438cdd9cb72d253feb RNReanimated: 75df06d3a81fc147b83056ae469512f573365b1d SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d Yoga: 1354c027ab07c7736f99a3bef16172d6f1b12b47 From 3ced129c06e5e52f78aafb5fc871351735069e00 Mon Sep 17 00:00:00 2001 From: Tomek Zawadzki Date: Sun, 22 Dec 2024 10:13:10 +0100 Subject: [PATCH 06/15] Unify `NSMutableParagraphStyle` creation --- apple/MarkdownFormatter.mm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apple/MarkdownFormatter.mm b/apple/MarkdownFormatter.mm index 5877e634..06917589 100644 --- a/apple/MarkdownFormatter.mm +++ b/apple/MarkdownFormatter.mm @@ -113,7 +113,8 @@ - (void)applyRangeToAttributedString:(NSMutableAttributedString *)attributedStri [attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range]; [attributedString addAttribute:RCTLiveMarkdownBlockquoteDepthAttributeName value:@(depth) range:range]; } else if (type == "h1" && markdownStyle.h1LineHeight != -1) { - NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new]; + NSParagraphStyle *defaultParagraphStyle = defaultTextAttributes[NSParagraphStyleAttributeName]; + NSMutableParagraphStyle *paragraphStyle = defaultParagraphStyle != nil ? [defaultParagraphStyle mutableCopy] : [NSMutableParagraphStyle new]; paragraphStyle.lineHeightMultiple = markdownStyle.h1LineHeight / markdownStyle.h1FontSize; NSRange rangeWithHashAndSpace = NSMakeRange(range.location - 2, range.length + 2); [attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:rangeWithHashAndSpace]; From f0451e0f9d8a8ef3a46b9ae2a665a14dc0b338b9 Mon Sep 17 00:00:00 2001 From: Tomek Zawadzki Date: Fri, 14 Mar 2025 12:18:57 +0100 Subject: [PATCH 07/15] Fix line height for h1 on iOS when line height is set on text input --- apple/MarkdownFormatter.mm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apple/MarkdownFormatter.mm b/apple/MarkdownFormatter.mm index 06917589..447ba26a 100644 --- a/apple/MarkdownFormatter.mm +++ b/apple/MarkdownFormatter.mm @@ -115,7 +115,8 @@ - (void)applyRangeToAttributedString:(NSMutableAttributedString *)attributedStri } else if (type == "h1" && markdownStyle.h1LineHeight != -1) { NSParagraphStyle *defaultParagraphStyle = defaultTextAttributes[NSParagraphStyleAttributeName]; NSMutableParagraphStyle *paragraphStyle = defaultParagraphStyle != nil ? [defaultParagraphStyle mutableCopy] : [NSMutableParagraphStyle new]; - paragraphStyle.lineHeightMultiple = markdownStyle.h1LineHeight / markdownStyle.h1FontSize; + paragraphStyle.minimumLineHeight = markdownStyle.h1LineHeight; + paragraphStyle.maximumLineHeight = markdownStyle.h1LineHeight; NSRange rangeWithHashAndSpace = NSMakeRange(range.location - 2, range.length + 2); [attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:rangeWithHashAndSpace]; } else if (type == "pre") { From 6f9838df7797b1ceea6cd9fda3beed47d871b1b5 Mon Sep 17 00:00:00 2001 From: Tomek Zawadzki Date: Fri, 14 Mar 2025 15:22:52 +0100 Subject: [PATCH 08/15] Add patch for react-native --- patches/react-native+0.77.0.patch | 57 +++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 patches/react-native+0.77.0.patch diff --git a/patches/react-native+0.77.0.patch b/patches/react-native+0.77.0.patch new file mode 100644 index 00000000..1a7aeb1a --- /dev/null +++ b/patches/react-native+0.77.0.patch @@ -0,0 +1,57 @@ +diff --git a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h b/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h +index 3427663..903344a 100644 +--- a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h ++++ b/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h +@@ -41,6 +41,8 @@ NSString *RCTNSStringFromStringApplyingTextTransform(NSString *string, facebook: + + void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText); + ++void RCTApplyBaselineOffsetForRange(NSMutableAttributedString *attributedText, NSRange attributedTextRange); ++ + /* + * Whether two `NSAttributedString` lead to the same underlying displayed text, even if they are not strictly equal. + * I.e. is one string substitutable for the other when backing a control (which may have some ignorable attributes +diff --git a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm b/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm +index 1e551e4..04c9d3d 100644 +--- a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm ++++ b/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm +@@ -325,11 +325,20 @@ NSMutableDictionary *RCTNSTextAttributesFromTextAttri + } + + void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText) ++{ ++ [attributedText.string enumerateSubstringsInRange:NSMakeRange(0, attributedText.length) ++ options:NSStringEnumerationByLines | NSStringEnumerationSubstringNotRequired ++ usingBlock:^(NSString * _Nullable substring, NSRange substringRange, NSRange enclosingRange, BOOL * _Nonnull stop) { ++ RCTApplyBaselineOffsetForRange(attributedText, enclosingRange); ++ }]; ++} ++ ++void RCTApplyBaselineOffsetForRange(NSMutableAttributedString *attributedText, NSRange attributedTextRange) + { + __block CGFloat maximumLineHeight = 0; + + [attributedText enumerateAttribute:NSParagraphStyleAttributeName +- inRange:NSMakeRange(0, attributedText.length) ++ inRange:attributedTextRange + options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired + usingBlock:^(NSParagraphStyle *paragraphStyle, __unused NSRange range, __unused BOOL *stop) { + if (!paragraphStyle) { +@@ -347,7 +356,7 @@ void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText) + __block CGFloat maximumFontLineHeight = 0; + + [attributedText enumerateAttribute:NSFontAttributeName +- inRange:NSMakeRange(0, attributedText.length) ++ inRange:attributedTextRange + options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired + usingBlock:^(UIFont *font, NSRange range, __unused BOOL *stop) { + if (!font) { +@@ -365,7 +374,7 @@ void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText) + + [attributedText addAttribute:NSBaselineOffsetAttributeName + value:@(baseLineOffset) +- range:NSMakeRange(0, attributedText.length)]; ++ range:attributedTextRange]; + } + + static NSMutableAttributedString *RCTNSAttributedStringFragmentFromFragment( From 16862ac6172f6d8af6fb13a8c5fe4c9c8f680c61 Mon Sep 17 00:00:00 2001 From: Tomek Zawadzki Date: Fri, 14 Mar 2025 15:43:32 +0100 Subject: [PATCH 09/15] Fix heading line height in blockquote --- apple/MarkdownFormatter.mm | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/apple/MarkdownFormatter.mm b/apple/MarkdownFormatter.mm index 447ba26a..e2bee19c 100644 --- a/apple/MarkdownFormatter.mm +++ b/apple/MarkdownFormatter.mm @@ -113,12 +113,27 @@ - (void)applyRangeToAttributedString:(NSMutableAttributedString *)attributedStri [attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range]; [attributedString addAttribute:RCTLiveMarkdownBlockquoteDepthAttributeName value:@(depth) range:range]; } else if (type == "h1" && markdownStyle.h1LineHeight != -1) { - NSParagraphStyle *defaultParagraphStyle = defaultTextAttributes[NSParagraphStyleAttributeName]; - NSMutableParagraphStyle *paragraphStyle = defaultParagraphStyle != nil ? [defaultParagraphStyle mutableCopy] : [NSMutableParagraphStyle new]; - paragraphStyle.minimumLineHeight = markdownStyle.h1LineHeight; - paragraphStyle.maximumLineHeight = markdownStyle.h1LineHeight; - NSRange rangeWithHashAndSpace = NSMakeRange(range.location - 2, range.length + 2); - [attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:rangeWithHashAndSpace]; + __block BOOL found = NO; + [attributedString enumerateAttribute:NSParagraphStyleAttributeName + inRange:range + options:0 + usingBlock:^(NSParagraphStyle *paragraphStyle, NSRange paragraphRange, BOOL *stop) { + if (paragraphStyle && [paragraphStyle isKindOfClass:[NSMutableParagraphStyle class]]) { + NSMutableParagraphStyle *mutableParagraphStyle = (NSMutableParagraphStyle *)paragraphStyle; + mutableParagraphStyle.minimumLineHeight = markdownStyle.h1LineHeight; + mutableParagraphStyle.maximumLineHeight = markdownStyle.h1LineHeight; + found = YES; + *stop = YES; + } + }]; + if (!found) { + NSParagraphStyle *defaultParagraphStyle = defaultTextAttributes[NSParagraphStyleAttributeName]; + NSMutableParagraphStyle *paragraphStyle = defaultParagraphStyle != nil ? [defaultParagraphStyle mutableCopy] : [NSMutableParagraphStyle new]; + paragraphStyle.minimumLineHeight = markdownStyle.h1LineHeight; + paragraphStyle.maximumLineHeight = markdownStyle.h1LineHeight; + NSRange rangeWithHashAndSpace = NSMakeRange(range.location - 2, range.length + 2); + [attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:rangeWithHashAndSpace]; + } } else if (type == "pre") { [attributedString addAttribute:NSForegroundColorAttributeName value:markdownStyle.preColor range:range]; NSRange rangeForBackground = [[attributedString string] characterAtIndex:range.location] == '\n' ? NSMakeRange(range.location + 1, range.length - 1) : range; From 7ceeadc7e3ec3fb28733c0052e11c1fc49a18c1c Mon Sep 17 00:00:00 2001 From: Tomek Zawadzki Date: Fri, 14 Mar 2025 16:12:32 +0100 Subject: [PATCH 10/15] Fix line height of regular lines --- apple/MarkdownFormatter.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apple/MarkdownFormatter.mm b/apple/MarkdownFormatter.mm index e2bee19c..716c5a06 100644 --- a/apple/MarkdownFormatter.mm +++ b/apple/MarkdownFormatter.mm @@ -118,7 +118,7 @@ - (void)applyRangeToAttributedString:(NSMutableAttributedString *)attributedStri inRange:range options:0 usingBlock:^(NSParagraphStyle *paragraphStyle, NSRange paragraphRange, BOOL *stop) { - if (paragraphStyle && [paragraphStyle isKindOfClass:[NSMutableParagraphStyle class]]) { + if (paragraphStyle && paragraphStyle.headIndent && [paragraphStyle isKindOfClass:[NSMutableParagraphStyle class]]) { NSMutableParagraphStyle *mutableParagraphStyle = (NSMutableParagraphStyle *)paragraphStyle; mutableParagraphStyle.minimumLineHeight = markdownStyle.h1LineHeight; mutableParagraphStyle.maximumLineHeight = markdownStyle.h1LineHeight; From bae14a5b4a9a20cb784f58efa92cc9d55f7c19bc Mon Sep 17 00:00:00 2001 From: Tomek Zawadzki Date: Tue, 18 Mar 2025 12:03:57 +0100 Subject: [PATCH 11/15] Fix cursor height in empty text input with line height set --- apple/MarkdownBackedTextInputDelegate.mm | 15 +++++++++++---- apple/RCTTextInputComponentView+Markdown.mm | 15 +++++++++++---- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/apple/MarkdownBackedTextInputDelegate.mm b/apple/MarkdownBackedTextInputDelegate.mm index 5c934969..2e610a9b 100644 --- a/apple/MarkdownBackedTextInputDelegate.mm +++ b/apple/MarkdownBackedTextInputDelegate.mm @@ -29,10 +29,17 @@ - (void)textInputDidChangeSelection // After adding a newline at the end of the blockquote, the typing attributes in the next line still contain // NSParagraphStyle with non-zero firstLineHeadIndent and headIntent added by `_updateTypingAttributes` call. // This causes the cursor to be shifted to the right instead of being located at the beginning of the line. - // The following code removes NSParagraphStyle from typing attributes to fix the position of the cursor. - NSMutableDictionary *typingAttributes = [_textView.typingAttributes mutableCopy]; - [typingAttributes removeObjectForKey:NSParagraphStyleAttributeName]; - _textView.typingAttributes = typingAttributes; + // The following code resets firstLineHeadIndent and headIndent in NSParagraphStyle in typing attributes + // in order to fix the position of the cursor. + NSDictionary *typingAttributes = _textView.typingAttributes; + if (typingAttributes[NSParagraphStyleAttributeName] != nil) { + NSMutableDictionary *mutableTypingAttributes = [typingAttributes mutableCopy]; + NSMutableParagraphStyle *mutableParagraphStyle = [typingAttributes[NSParagraphStyleAttributeName] mutableCopy]; + mutableParagraphStyle.firstLineHeadIndent = 0; + mutableParagraphStyle.headIndent = 0; + mutableTypingAttributes[NSParagraphStyleAttributeName] = mutableParagraphStyle; + _textView.typingAttributes = mutableTypingAttributes; + } } // Delegate all remaining calls to the original text input delegate diff --git a/apple/RCTTextInputComponentView+Markdown.mm b/apple/RCTTextInputComponentView+Markdown.mm index 8ab34f50..c6f3ee7b 100644 --- a/apple/RCTTextInputComponentView+Markdown.mm +++ b/apple/RCTTextInputComponentView+Markdown.mm @@ -49,10 +49,17 @@ - (void)markdown__setAttributedString:(NSAttributedString *)attributedString // After adding a newline at the end of the blockquote, the typing attributes in the next line still contain // NSParagraphStyle with non-zero firstLineHeadIndent and headIntent added by `_updateTypingAttributes` call. // This causes the cursor to be shifted to the right instead of being located at the beginning of the line. - // The following code removes NSParagraphStyle from typing attributes to fix the position of the cursor. - NSMutableDictionary *typingAttributes = [backedTextInputView.typingAttributes mutableCopy]; - [typingAttributes removeObjectForKey:NSParagraphStyleAttributeName]; - backedTextInputView.typingAttributes = typingAttributes; + // The following code resets firstLineHeadIndent and headIndent in NSParagraphStyle in typing attributes + // in order to fix the position of the cursor. + NSDictionary *typingAttributes = backedTextInputView.typingAttributes; + if (typingAttributes[NSParagraphStyleAttributeName] != nil) { + NSMutableDictionary *mutableTypingAttributes = [typingAttributes mutableCopy]; + NSMutableParagraphStyle *mutableParagraphStyle = [typingAttributes[NSParagraphStyleAttributeName] mutableCopy]; + mutableParagraphStyle.firstLineHeadIndent = 0; + mutableParagraphStyle.headIndent = 0; + mutableTypingAttributes[NSParagraphStyleAttributeName] = mutableParagraphStyle; + backedTextInputView.typingAttributes = mutableTypingAttributes; + } } } From 1850eb87003dec79cb168f02ff9a35feeea71d22 Mon Sep 17 00:00:00 2001 From: Tomek Zawadzki Date: Wed, 19 Mar 2025 15:12:45 +0100 Subject: [PATCH 12/15] WIP --- apple/MarkdownBackedTextInputDelegate.mm | 2 ++ apple/RCTTextInputComponentView+Markdown.mm | 2 ++ 2 files changed, 4 insertions(+) diff --git a/apple/MarkdownBackedTextInputDelegate.mm b/apple/MarkdownBackedTextInputDelegate.mm index 2e610a9b..355cfb28 100644 --- a/apple/MarkdownBackedTextInputDelegate.mm +++ b/apple/MarkdownBackedTextInputDelegate.mm @@ -37,6 +37,8 @@ - (void)textInputDidChangeSelection NSMutableParagraphStyle *mutableParagraphStyle = [typingAttributes[NSParagraphStyleAttributeName] mutableCopy]; mutableParagraphStyle.firstLineHeadIndent = 0; mutableParagraphStyle.headIndent = 0; + mutableParagraphStyle.minimumLineHeight = 0; + mutableParagraphStyle.maximumLineHeight = 0; mutableTypingAttributes[NSParagraphStyleAttributeName] = mutableParagraphStyle; _textView.typingAttributes = mutableTypingAttributes; } diff --git a/apple/RCTTextInputComponentView+Markdown.mm b/apple/RCTTextInputComponentView+Markdown.mm index c6f3ee7b..44e794e8 100644 --- a/apple/RCTTextInputComponentView+Markdown.mm +++ b/apple/RCTTextInputComponentView+Markdown.mm @@ -57,6 +57,8 @@ - (void)markdown__setAttributedString:(NSAttributedString *)attributedString NSMutableParagraphStyle *mutableParagraphStyle = [typingAttributes[NSParagraphStyleAttributeName] mutableCopy]; mutableParagraphStyle.firstLineHeadIndent = 0; mutableParagraphStyle.headIndent = 0; + mutableParagraphStyle.minimumLineHeight = 0; + mutableParagraphStyle.maximumLineHeight = 0; mutableTypingAttributes[NSParagraphStyleAttributeName] = mutableParagraphStyle; backedTextInputView.typingAttributes = mutableTypingAttributes; } From 914b5b9d9131a1f163f1c223b8eca058073708b8 Mon Sep 17 00:00:00 2001 From: Tomek Zawadzki Date: Mon, 14 Apr 2025 10:58:42 +0200 Subject: [PATCH 13/15] Fix react-native patch --- example/ios/Podfile.lock | 4 ++-- ...t-native+0.77.0.patch => react-native+0.79.0.patch} | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) rename patches/{react-native+0.77.0.patch => react-native+0.79.0.patch} (93%) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 9e263ab1..099c827c 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1654,7 +1654,7 @@ PODS: - React-logger (= 0.79.0) - React-perflogger (= 0.79.0) - React-utils (= 0.79.0) - - RNLiveMarkdown (0.1.263): + - RNLiveMarkdown (0.1.264): - DoubleConversion - glog - hermes-engine @@ -2103,7 +2103,7 @@ SPEC CHECKSUMS: ReactAppDependencyProvider: 7f5052913dd72a0e84ca95973f9f8bc97f2c5b95 ReactCodegen: 95b7922e777426e40cb306fd9596f792e66f26e9 ReactCommon: 1257efa9d0b07517d8b79bb4055eaefac1204807 - RNLiveMarkdown: 469808d6f4431a3b60e3bc402fd7cacc2346f161 + RNLiveMarkdown: 16383fcbaf2b96453c469f33c0107a3c4e9b242c RNReanimated: 8011ddcc1f6bc2fc1f27a5ac384d7495829ab6a0 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: 7fb3f48a328f20ea5d5eecd862e91798bd76b255 diff --git a/patches/react-native+0.77.0.patch b/patches/react-native+0.79.0.patch similarity index 93% rename from patches/react-native+0.77.0.patch rename to patches/react-native+0.79.0.patch index 1a7aeb1a..1a75cbed 100644 --- a/patches/react-native+0.77.0.patch +++ b/patches/react-native+0.79.0.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h b/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h -index 3427663..903344a 100644 +index 908cfc0..f693c17 100644 --- a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h +++ b/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h @@ -41,6 +41,8 @@ NSString *RCTNSStringFromStringApplyingTextTransform(NSString *string, facebook: @@ -12,10 +12,10 @@ index 3427663..903344a 100644 * Whether two `NSAttributedString` lead to the same underlying displayed text, even if they are not strictly equal. * I.e. is one string substitutable for the other when backing a control (which may have some ignorable attributes diff --git a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm b/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm -index 1e551e4..04c9d3d 100644 +index d08d0ba..21b32a1 100644 --- a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm +++ b/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm -@@ -325,11 +325,20 @@ NSMutableDictionary *RCTNSTextAttributesFromTextAttri +@@ -292,11 +292,20 @@ NSMutableDictionary *RCTNSTextAttributesFromTextAttri } void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText) @@ -37,7 +37,7 @@ index 1e551e4..04c9d3d 100644 options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(NSParagraphStyle *paragraphStyle, __unused NSRange range, __unused BOOL *stop) { if (!paragraphStyle) { -@@ -347,7 +356,7 @@ void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText) +@@ -314,7 +323,7 @@ void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText) __block CGFloat maximumFontLineHeight = 0; [attributedText enumerateAttribute:NSFontAttributeName @@ -46,7 +46,7 @@ index 1e551e4..04c9d3d 100644 options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(UIFont *font, NSRange range, __unused BOOL *stop) { if (!font) { -@@ -365,7 +374,7 @@ void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText) +@@ -332,7 +341,7 @@ void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText) [attributedText addAttribute:NSBaselineOffsetAttributeName value:@(baseLineOffset) From 121b311378407dcabdb459dec2e3421ad7651bd1 Mon Sep 17 00:00:00 2001 From: Tomek Zawadzki Date: Thu, 15 May 2025 11:48:16 +0200 Subject: [PATCH 14/15] Bump react-native patch version --- patches/{react-native+0.79.0.patch => react-native+0.79.2.patch} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename patches/{react-native+0.79.0.patch => react-native+0.79.2.patch} (100%) diff --git a/patches/react-native+0.79.0.patch b/patches/react-native+0.79.2.patch similarity index 100% rename from patches/react-native+0.79.0.patch rename to patches/react-native+0.79.2.patch From 11ef181918202b3e1e3267615b65eb57dcbc6681 Mon Sep 17 00:00:00 2001 From: Tomek Zawadzki Date: Thu, 15 May 2025 12:47:15 +0200 Subject: [PATCH 15/15] Add buttons for toggling text and heading line height --- example/src/App.tsx | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/example/src/App.tsx b/example/src/App.tsx index b1451ab4..ba807052 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -15,6 +15,9 @@ export default function App() { const [linkColorState, setLinkColorState] = React.useState(false); const [textFontSizeState, setTextFontSizeState] = React.useState(false); const [emojiFontSizeState, setEmojiFontSizeState] = React.useState(false); + const [textLineHeightState, setTextLineHeightState] = React.useState(false); + const [headingLineHeightState, setHeadingLineHeightState] = + React.useState(false); const [caretHidden, setCaretHidden] = React.useState(false); const [selection, setSelection] = React.useState({start: 0, end: 0}); @@ -22,8 +25,9 @@ export default function App() { return { color: textColorState ? 'gray' : 'black', fontSize: textFontSizeState ? 15 : 20, + lineHeight: textLineHeightState ? 40 : undefined, }; - }, [textColorState, textFontSizeState]); + }, [textColorState, textFontSizeState, textLineHeightState]); const markdownStyle = React.useMemo(() => { return { @@ -34,17 +38,15 @@ export default function App() { color: linkColorState ? 'red' : 'blue', }, h1: { - lineHeight: 50, + lineHeight: headingLineHeightState ? 60 : undefined, }, }; - }, [emojiFontSizeState, linkColorState]); + }, [emojiFontSizeState, linkColorState, headingLineHeightState]); const ref = React.useRef(null); return ( - + setEmojiFontSizeState(prev => !prev)} /> +