Skip to content

Commit

Permalink
Implement code and pre blocks support on iOS
Browse files Browse the repository at this point in the history
  • Loading branch information
maksg committed May 21, 2024
1 parent 4cbff0f commit 589feb1
Show file tree
Hide file tree
Showing 7 changed files with 304 additions and 139 deletions.
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 @@ -18,10 +18,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

0 comments on commit 589feb1

Please sign in to comment.