Skip to content

Commit

Permalink
Move formatting logic to separate class on iOS (#572)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomekzaw authored Dec 9, 2024
1 parent 4fbf007 commit 0f9011b
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 160 deletions.
18 changes: 18 additions & 0 deletions apple/MarkdownFormatter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#import <Foundation/Foundation.h>
#import <RNLiveMarkdown/MarkdownRange.h>
#import <RNLiveMarkdown/RCTMarkdownStyle.h>

NS_ASSUME_NONNULL_BEGIN

const NSAttributedStringKey RCTLiveMarkdownBlockquoteDepthAttributeName = @"RCTLiveMarkdownBlockquoteDepth";

@interface MarkdownFormatter : NSObject

- (nonnull NSAttributedString *)format:(nonnull NSString *)text
withAttributes:(nullable NSDictionary<NSAttributedStringKey, id>*)attributes
withMarkdownRanges:(nonnull NSArray<MarkdownRange *> *)markdownRanges
withMarkdownStyle:(nonnull RCTMarkdownStyle *)markdownStyle;

NS_ASSUME_NONNULL_END

@end
158 changes: 158 additions & 0 deletions apple/MarkdownFormatter.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
#import "MarkdownFormatter.h"
#import <React/RCTFont.h>

@implementation MarkdownFormatter

- (nonnull NSAttributedString *)format:(nonnull NSString *)text
withAttributes:(nullable NSDictionary<NSAttributedStringKey, id> *)attributes
withMarkdownRanges:(nonnull NSArray<MarkdownRange *> *)markdownRanges
withMarkdownStyle:(nonnull RCTMarkdownStyle *)markdownStyle
{
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:text attributes:attributes];

[attributedString beginEditing];

// If the attributed string ends with underlined text, blurring the single-line input imprints the underline style across the whole string.
// It looks like a bug in iOS, as there is no underline style to be found in the attributed string, especially after formatting.
// This is a workaround that applies the NSUnderlineStyleNone to the string before iterating over ranges which resolves this problem.
[attributedString addAttribute:NSUnderlineStyleAttributeName
value:[NSNumber numberWithInteger:NSUnderlineStyleNone]
range:NSMakeRange(0, attributedString.length)];

for (MarkdownRange *markdownRange in markdownRanges) {
[self applyRangeToAttributedString:attributedString
type:std::string([markdownRange.type UTF8String])
range:markdownRange.range
depth:markdownRange.depth
markdownStyle:markdownStyle];
}

RCTApplyBaselineOffset(attributedString);

[attributedString endEditing];

return attributedString;
}

- (void)applyRangeToAttributedString:(NSMutableAttributedString *)attributedString
type:(const std::string)type
range:(const NSRange)range
depth:(const int)depth
markdownStyle:(nonnull RCTMarkdownStyle *)markdownStyle {
if (type == "bold" || type == "italic" || type == "code" || type == "pre" || type == "h1" || type == "emoji") {
UIFont *font = [attributedString attribute:NSFontAttributeName atIndex:range.location effectiveRange:NULL];
if (type == "bold") {
font = [RCTFont updateFont:font withWeight:@"bold"];
} else if (type == "italic") {
font = [RCTFont updateFont:font withStyle:@"italic"];
} else if (type == "code") {
font = [RCTFont updateFont:font withFamily:markdownStyle.codeFontFamily
size:[NSNumber numberWithFloat:markdownStyle.codeFontSize]
weight:nil
style:nil
variant:nil
scaleMultiplier:0];
} else if (type == "pre") {
font = [RCTFont updateFont:font withFamily:markdownStyle.preFontFamily
size:[NSNumber numberWithFloat:markdownStyle.preFontSize]
weight:nil
style:nil
variant:nil
scaleMultiplier:0];
} else if (type == "h1") {
font = [RCTFont updateFont:font withFamily:nil
size:[NSNumber numberWithFloat:markdownStyle.h1FontSize]
weight:@"bold"
style:nil
variant:nil
scaleMultiplier:0];
} else if (type == "emoji") {
font = [RCTFont updateFont:font withFamily:nil
size:[NSNumber numberWithFloat:markdownStyle.emojiFontSize]
weight:nil
style:nil
variant:nil
scaleMultiplier:0];
}
[attributedString addAttribute:NSFontAttributeName value:font range:range];
}

if (type == "syntax") {
[attributedString addAttribute:NSForegroundColorAttributeName value:markdownStyle.syntaxColor range:range];
} else if (type == "strikethrough") {
[attributedString addAttribute:NSStrikethroughStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:range];
} else if (type == "code") {
[attributedString addAttribute:NSForegroundColorAttributeName value:markdownStyle.codeColor range:range];
[attributedString addAttribute:NSBackgroundColorAttributeName value:markdownStyle.codeBackgroundColor range:range];
} else if (type == "mention-here") {
[attributedString addAttribute:NSForegroundColorAttributeName value:markdownStyle.mentionHereColor range:range];
[attributedString addAttribute:NSBackgroundColorAttributeName value:markdownStyle.mentionHereBackgroundColor range:range];
} else if (type == "mention-user") {
// TODO: change mention color when it mentions current user
[attributedString addAttribute:NSForegroundColorAttributeName value:markdownStyle.mentionUserColor range:range];
[attributedString addAttribute:NSBackgroundColorAttributeName value:markdownStyle.mentionUserBackgroundColor range:range];
} else if (type == "mention-report") {
[attributedString addAttribute:NSForegroundColorAttributeName value:markdownStyle.mentionReportColor range:range];
[attributedString addAttribute:NSBackgroundColorAttributeName value:markdownStyle.mentionReportBackgroundColor range:range];
} else if (type == "link") {
[attributedString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:range];
[attributedString addAttribute:NSForegroundColorAttributeName value:markdownStyle.linkColor range:range];
} else if (type == "blockquote") {
CGFloat indent = (markdownStyle.blockquoteMarginLeft + markdownStyle.blockquoteBorderWidth + markdownStyle.blockquotePaddingLeft) * depth;
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.firstLineHeadIndent = indent;
paragraphStyle.headIndent = indent;
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range];
[attributedString addAttribute:RCTLiveMarkdownBlockquoteDepthAttributeName value:@(depth) range:range];
} 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;
[attributedString addAttribute:NSBackgroundColorAttributeName value:markdownStyle.preBackgroundColor range:rangeForBackground];
// TODO: pass background color and ranges to layout manager
}
}

static void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText)
{
__block CGFloat maximumLineHeight = 0;

[attributedText enumerateAttribute:NSParagraphStyleAttributeName
inRange:NSMakeRange(0, attributedText.length)
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
usingBlock:^(NSParagraphStyle *paragraphStyle, __unused NSRange range, __unused BOOL *stop) {
if (!paragraphStyle) {
return;
}

maximumLineHeight = MAX(paragraphStyle.maximumLineHeight, maximumLineHeight);
}];

if (maximumLineHeight == 0) {
// `lineHeight` was not specified, nothing to do.
return;
}

__block CGFloat maximumFontLineHeight = 0;

[attributedText enumerateAttribute:NSFontAttributeName
inRange:NSMakeRange(0, attributedText.length)
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
usingBlock:^(UIFont *font, NSRange range, __unused BOOL *stop) {
if (!font) {
return;
}

maximumFontLineHeight = MAX(font.lineHeight, maximumFontLineHeight);
}];

if (maximumLineHeight < maximumFontLineHeight) {
return;
}

CGFloat baseLineOffset = (maximumLineHeight - maximumFontLineHeight) / 2.0;
[attributedText addAttribute:NSBaselineOffsetAttributeName
value:@(baseLineOffset)
range:NSMakeRange(0, attributedText.length)];
}

@end
1 change: 1 addition & 0 deletions apple/MarkdownLayoutManager.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#import <UIKit/UIKit.h>
#import <RNLiveMarkdown/RCTMarkdownUtils.h>
#import <RNLiveMarkdown/MarkdownFormatter.h>

NS_ASSUME_NONNULL_BEGIN

Expand Down
2 changes: 0 additions & 2 deletions apple/RCTMarkdownUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@

NS_ASSUME_NONNULL_BEGIN

const NSAttributedStringKey RCTLiveMarkdownBlockquoteDepthAttributeName = @"RCTLiveMarkdownBlockquoteDepth";

@interface RCTMarkdownUtils : NSObject

@property (nonatomic) RCTMarkdownStyle *markdownStyle;
Expand Down
Loading

0 comments on commit 0f9011b

Please sign in to comment.