Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement code and pre blocks support on iOS #306

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 129 additions & 10 deletions ios/MarkdownLayoutManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,39 @@

@implementation MarkdownLayoutManager

- (BOOL)isRange:(NSRange)smallerRange inRange:(NSRange)largerRange {
NSUInteger start = smallerRange.location;
NSUInteger end = start + smallerRange.length;
NSUInteger location = largerRange.location;
return location >= start && location < end;
}

- (CGRect)rectByAddingPadding:(CGFloat)padding toRect:(CGRect)rect {
rect.origin.x -= padding;
rect.origin.y -= padding;
rect.size.width += padding * 2;
rect.size.height += padding * 2;
return rect;
}

- (void)drawBackgroundForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origin {
[super drawBackgroundForGlyphRange:glyphsToShow atPoint:origin];

RCTMarkdownStyle *style = [_markdownUtils markdownStyle];
[self drawBlockquotesForRanges:[_markdownUtils blockquoteRangesAndLevels] andGlyphRange:glyphsToShow atPoint:origin withColor:[style blockquoteBorderColor] width:[style blockquoteBorderWidth] margin:[style blockquoteMarginLeft] andPadding:[style blockquotePaddingLeft]];
[self drawPreBackgroundForRanges:[_markdownUtils preRanges] atPoint:origin withColor:[style preBackgroundColor] borderColor:[style preBorderColor] borderWidth:[style preBorderWidth] borderRadius:[style preBorderRadius] andPadding:[style prePadding]];
[self drawCodeBackgroundForRanges:[_markdownUtils codeRanges] atPoint:origin withColor:[style codeBackgroundColor] borderColor:[style codeBorderColor] borderWidth:[style codeBorderWidth] borderRadius:[style codeBorderRadius] andPadding:[style codePadding]];
}

- (void)drawBlockquotesForRanges:(NSArray<NSDictionary*>*)ranges andGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origin withColor:(UIColor*)color width:(CGFloat)width margin:(CGFloat)margin andPadding:(CGFloat)padding {
[self enumerateLineFragmentsForGlyphRange:glyphsToShow usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer * _Nonnull textContainer, NSRange glyphRange, BOOL * _Nonnull stop) {
__block BOOL isBlockquote = NO;
__block int currentDepth = 0;
RCTMarkdownUtils *markdownUtils = [self valueForKey:@"markdownUtils"];
[markdownUtils.blockquoteRangesAndLevels enumerateObjectsUsingBlock:^(NSDictionary *item, NSUInteger idx, BOOL * _Nonnull stop) {

[ranges enumerateObjectsUsingBlock:^(NSDictionary *item, NSUInteger idx, BOOL * _Nonnull stop) {
NSRange range = [[item valueForKey:@"range"] rangeValue];
currentDepth = [[item valueForKey:@"depth"] unsignedIntegerValue];
NSUInteger start = range.location;
NSUInteger end = start + range.length;
NSUInteger location = glyphRange.location;
if (location >= start && location < end) {
if ([self isRange:range inRange:glyphRange]) {
isBlockquote = YES;
*stop = YES;
}
Expand All @@ -24,17 +43,117 @@ - (void)drawBackgroundForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origi
CGFloat paddingLeft = origin.x;
CGFloat paddingTop = origin.y;
CGFloat y = paddingTop + rect.origin.y;
CGFloat width = markdownUtils.markdownStyle.blockquoteBorderWidth;
CGFloat height = rect.size.height;
CGFloat shift = markdownUtils.markdownStyle.blockquoteMarginLeft + markdownUtils.markdownStyle.blockquoteBorderWidth + markdownUtils.markdownStyle.blockquotePaddingLeft;
CGFloat shift = margin + width + padding;
for (int level = 0; level < currentDepth; level++) {
CGFloat x = paddingLeft + (level * shift) + markdownUtils.markdownStyle.blockquoteMarginLeft;
CGFloat x = paddingLeft + (level * shift) + margin;
CGRect lineRect = CGRectMake(x, y, width, height);
[markdownUtils.markdownStyle.blockquoteBorderColor setFill];
[color setFill];
UIRectFill(lineRect);
}
}
}];
}

- (void)drawPreBackgroundForRanges:(NSArray<NSValue*>*)ranges atPoint:(CGPoint)origin withColor:(UIColor*)backgroundColor borderColor:(UIColor*)borderColor borderWidth:(CGFloat)borderWidth borderRadius:(CGFloat)borderRadius andPadding:(CGFloat)padding {
__block CGRect preRect = CGRectNull;
[ranges enumerateObjectsUsingBlock:^(NSValue *item, NSUInteger idx, BOOL * _Nonnull stop) {
NSRange range = [item rangeValue];
// We don't want the trailing ``` to be a part of the block so we need to reduce range by 1.
// This also breaks one character blocks so we need to check if range is larger.
if (range.length > 1) {
range.location += 1;
range.length -= 1;
}

[self enumerateLineFragmentsForGlyphRange:range usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer * _Nonnull textContainer, NSRange glyphRange, BOOL * _Nonnull stop) {
if (CGRectIsNull(preRect)) {
preRect = usedRect;
CGFloat paddingLeft = origin.x;
preRect.origin.x += paddingLeft;
CGFloat paddingTop = origin.y;
preRect.origin.y += paddingTop;
} else {
CGFloat usedWidth = usedRect.size.width;
if (usedWidth > preRect.size.width) {
preRect.size.width = usedWidth;
}
preRect.size.height += usedRect.size.height;
}
}];

if (!CGRectIsNull(preRect)) {
preRect = [self rectByAddingPadding:padding toRect:preRect];
[self drawBackgroundWithColor:backgroundColor borderColor:borderColor borderWidth:borderWidth andBorderRadius:borderRadius forRect:preRect isLeftOpen:NO isRightOpen:NO];
preRect = CGRectNull;
}
}];
}

- (void)drawCodeBackgroundForRanges:(NSArray<NSValue*>*)ranges atPoint:(CGPoint)origin withColor:(UIColor*)backgroundColor borderColor:(UIColor*)borderColor borderWidth:(CGFloat)borderWidth borderRadius:(CGFloat)borderRadius andPadding:(CGFloat)padding {
[ranges enumerateObjectsUsingBlock:^(NSValue *item, NSUInteger idx, BOOL * _Nonnull stop) {
NSRange range = [item rangeValue];
[self enumerateLineFragmentsForGlyphRange:range usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer * _Nonnull textContainer, NSRange glyphRange, BOOL * _Nonnull stop) {
BOOL isLeftSideOpen = YES;
BOOL isRightSideOpen = YES;

NSRange adjustedRange = glyphRange;
if (range.location > adjustedRange.location) {
adjustedRange.length -= range.location - adjustedRange.location;
adjustedRange.location = range.location;
isLeftSideOpen = NO;
}

NSUInteger rangeEndLocation = range.location + range.length;
NSUInteger adjustedRangeEndLocation = adjustedRange.location + adjustedRange.length;
if (rangeEndLocation < adjustedRangeEndLocation) {
adjustedRange.length -= adjustedRangeEndLocation - rangeEndLocation;
isRightSideOpen = NO;
}

CGRect codeRect = [self boundingRectForGlyphRange:adjustedRange inTextContainer:textContainer];
CGFloat paddingLeft = origin.x;
codeRect.origin.x += paddingLeft;
CGFloat paddingTop = origin.y;
codeRect.origin.y += paddingTop;
codeRect = [self rectByAddingPadding:padding toRect:codeRect];
[self drawBackgroundWithColor:backgroundColor borderColor:borderColor borderWidth:borderWidth andBorderRadius:borderRadius forRect:codeRect isLeftOpen:isLeftSideOpen isRightOpen:isRightSideOpen];
}];
}];
}

- (void)drawBackgroundWithColor:(UIColor*)backgroundColor borderColor:(UIColor*)borderColor borderWidth:(CGFloat)borderWidth andBorderRadius:(CGFloat)radius forRect:(CGRect)rect isLeftOpen:(BOOL)isLeftOpen isRightOpen:(BOOL)isRightOpen {
UIRectCorner corners = 0;
if (!isLeftOpen) {
corners |= UIRectCornerTopLeft | UIRectCornerBottomLeft;
}
if (!isRightOpen) {
corners |= UIRectCornerTopRight | UIRectCornerBottomRight;
}
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:CGSizeMake(radius, radius)];

[backgroundColor setFill];
[path fill];
[borderColor setStroke];
[path setLineWidth:borderWidth];
[path stroke];

if (isLeftOpen) {
[self openSideForRect:rect withBorderWidth:borderWidth isLeft:YES];
}
if (isRightOpen) {
[self openSideForRect:rect withBorderWidth:borderWidth isLeft:NO];
}
}

- (void)openSideForRect:(CGRect)rect withBorderWidth:(CGFloat)borderWidth isLeft:(BOOL)isLeft {
UIBezierPath *path = [[UIBezierPath alloc] init];
CGFloat x = isLeft ? CGRectGetMinX(rect) : CGRectGetMaxX(rect);
[path moveToPoint:CGPointMake(x, CGRectGetMinY(rect) - borderWidth)];
[path addLineToPoint:CGPointMake(x, CGRectGetMaxY(rect) + borderWidth)];
[[UIColor clearColor] setStroke];
[path setLineWidth:borderWidth + 1];
[path strokeWithBlendMode:kCGBlendModeClear alpha:1.0];
}

@end
8 changes: 8 additions & 0 deletions ios/RCTMarkdownStyle.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,18 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic) CGFloat codeFontSize;
@property (nonatomic) UIColor *codeColor;
@property (nonatomic) UIColor *codeBackgroundColor;
@property (nonatomic) UIColor *codeBorderColor;
@property (nonatomic) CGFloat codeBorderWidth;
@property (nonatomic) CGFloat codeBorderRadius;
@property (nonatomic) CGFloat codePadding;
@property (nonatomic) NSString *preFontFamily;
@property (nonatomic) CGFloat preFontSize;
@property (nonatomic) UIColor *preColor;
@property (nonatomic) UIColor *preBackgroundColor;
@property (nonatomic) UIColor *preBorderColor;
@property (nonatomic) CGFloat preBorderWidth;
@property (nonatomic) CGFloat preBorderRadius;
@property (nonatomic) CGFloat prePadding;
@property (nonatomic) UIColor *mentionHereColor;
@property (nonatomic) UIColor *mentionHereBackgroundColor;
@property (nonatomic) UIColor *mentionUserColor;
Expand Down
16 changes: 16 additions & 0 deletions ios/RCTMarkdownStyle.mm
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,19 @@ - (instancetype)initWithStruct:(const facebook::react::MarkdownTextInputDecorato
_codeFontSize = style.code.fontSize;
_codeColor = RCTUIColorFromSharedColor(style.code.color);
_codeBackgroundColor = RCTUIColorFromSharedColor(style.code.backgroundColor);
_codeBorderColor = RCTUIColorFromSharedColor(style.code.borderColor);
_codeBorderWidth = style.code.borderWidth;
_codeBorderRadius = style.code.borderRadius;
_codePadding = style.code.padding;

_preFontFamily = RCTNSStringFromString(style.pre.fontFamily);
_preFontSize = style.pre.fontSize;
_preColor = RCTUIColorFromSharedColor(style.pre.color);
_preBackgroundColor = RCTUIColorFromSharedColor(style.pre.backgroundColor);
_preBorderColor = RCTUIColorFromSharedColor(style.pre.borderColor);
_preBorderWidth = style.pre.borderWidth;
_preBorderRadius = style.pre.borderRadius;
_prePadding = style.pre.padding;

_mentionHereColor = RCTUIColorFromSharedColor(style.mentionHere.color);
_mentionHereBackgroundColor = RCTUIColorFromSharedColor(style.mentionHere.backgroundColor);
Expand Down Expand Up @@ -71,11 +79,19 @@ - (instancetype)initWithDictionary:(NSDictionary *)json
_codeFontSize = [RCTConvert CGFloat:json[@"code"][@"fontSize"]];
_codeColor = [RCTConvert UIColor:json[@"code"][@"color"]];
_codeBackgroundColor = [RCTConvert UIColor:json[@"code"][@"backgroundColor"]];
_codeBorderColor = [RCTConvert UIColor:json[@"code"][@"borderColor"]];
_codeBorderWidth = [RCTConvert CGFloat:json[@"code"][@"borderWidth"]];
_codeBorderRadius = [RCTConvert CGFloat:json[@"code"][@"borderRadius"]];
_codePadding = [RCTConvert CGFloat:json[@"code"][@"padding"]];

_preFontFamily = [RCTConvert NSString:json[@"pre"][@"fontFamily"]];
_preFontSize = [RCTConvert CGFloat:json[@"pre"][@"fontSize"]];
_preColor = [RCTConvert UIColor:json[@"pre"][@"color"]];
_preBackgroundColor = [RCTConvert UIColor:json[@"pre"][@"backgroundColor"]];
_preBorderColor = [RCTConvert UIColor:json[@"pre"][@"borderColor"]];
_preBorderWidth = [RCTConvert CGFloat:json[@"pre"][@"borderWidth"]];
_preBorderRadius = [RCTConvert CGFloat:json[@"pre"][@"borderRadius"]];
_prePadding = [RCTConvert CGFloat:json[@"pre"][@"padding"]];

_mentionHereColor = [RCTConvert UIColor:json[@"mentionHere"][@"color"]];
_mentionHereBackgroundColor = [RCTConvert UIColor:json[@"mentionHere"][@"backgroundColor"]];
Expand Down
2 changes: 2 additions & 0 deletions ios/RCTMarkdownUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ NS_ASSUME_NONNULL_BEGIN

@property (nonatomic) RCTMarkdownStyle *markdownStyle;
@property (nonatomic) NSMutableArray<NSDictionary *> *blockquoteRangesAndLevels;
@property (nonatomic) NSMutableArray<NSValue *> *codeRanges;
@property (nonatomic) NSMutableArray<NSValue *> *preRanges;

- (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input withAttributes:(nullable NSDictionary<NSAttributedStringKey, id>*)attributes;

Expand Down
Loading
Loading