-
Notifications
You must be signed in to change notification settings - Fork 65
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into @tomekzaw/line-height
- Loading branch information
Showing
12 changed files
with
228 additions
and
177 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
#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 == "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; | ||
[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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.