Skip to content

Commit 589feb1

Browse files
committed
Implement code and pre blocks support on iOS
1 parent 4cbff0f commit 589feb1

7 files changed

+304
-139
lines changed

ios/MarkdownLayoutManager.mm

Lines changed: 129 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,39 @@
22

33
@implementation MarkdownLayoutManager
44

5+
- (BOOL)isRange:(NSRange)smallerRange inRange:(NSRange)largerRange {
6+
NSUInteger start = smallerRange.location;
7+
NSUInteger end = start + smallerRange.length;
8+
NSUInteger location = largerRange.location;
9+
return location >= start && location < end;
10+
}
11+
12+
- (CGRect)rectByAddingPadding:(CGFloat)padding toRect:(CGRect)rect {
13+
rect.origin.x -= padding;
14+
rect.origin.y -= padding;
15+
rect.size.width += padding * 2;
16+
rect.size.height += padding * 2;
17+
return rect;
18+
}
19+
520
- (void)drawBackgroundForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origin {
621
[super drawBackgroundForGlyphRange:glyphsToShow atPoint:origin];
722

23+
RCTMarkdownStyle *style = [_markdownUtils markdownStyle];
24+
[self drawBlockquotesForRanges:[_markdownUtils blockquoteRangesAndLevels] andGlyphRange:glyphsToShow atPoint:origin withColor:[style blockquoteBorderColor] width:[style blockquoteBorderWidth] margin:[style blockquoteMarginLeft] andPadding:[style blockquotePaddingLeft]];
25+
[self drawPreBackgroundForRanges:[_markdownUtils preRanges] atPoint:origin withColor:[style preBackgroundColor] borderColor:[style preBorderColor] borderWidth:[style preBorderWidth] borderRadius:[style preBorderRadius] andPadding:[style prePadding]];
26+
[self drawCodeBackgroundForRanges:[_markdownUtils codeRanges] atPoint:origin withColor:[style codeBackgroundColor] borderColor:[style codeBorderColor] borderWidth:[style codeBorderWidth] borderRadius:[style codeBorderRadius] andPadding:[style codePadding]];
27+
}
28+
29+
- (void)drawBlockquotesForRanges:(NSArray<NSDictionary*>*)ranges andGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origin withColor:(UIColor*)color width:(CGFloat)width margin:(CGFloat)margin andPadding:(CGFloat)padding {
830
[self enumerateLineFragmentsForGlyphRange:glyphsToShow usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer * _Nonnull textContainer, NSRange glyphRange, BOOL * _Nonnull stop) {
931
__block BOOL isBlockquote = NO;
1032
__block int currentDepth = 0;
11-
RCTMarkdownUtils *markdownUtils = [self valueForKey:@"markdownUtils"];
12-
[markdownUtils.blockquoteRangesAndLevels enumerateObjectsUsingBlock:^(NSDictionary *item, NSUInteger idx, BOOL * _Nonnull stop) {
33+
34+
[ranges enumerateObjectsUsingBlock:^(NSDictionary *item, NSUInteger idx, BOOL * _Nonnull stop) {
1335
NSRange range = [[item valueForKey:@"range"] rangeValue];
1436
currentDepth = [[item valueForKey:@"depth"] unsignedIntegerValue];
15-
NSUInteger start = range.location;
16-
NSUInteger end = start + range.length;
17-
NSUInteger location = glyphRange.location;
18-
if (location >= start && location < end) {
37+
if ([self isRange:range inRange:glyphRange]) {
1938
isBlockquote = YES;
2039
*stop = YES;
2140
}
@@ -24,17 +43,117 @@ - (void)drawBackgroundForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origi
2443
CGFloat paddingLeft = origin.x;
2544
CGFloat paddingTop = origin.y;
2645
CGFloat y = paddingTop + rect.origin.y;
27-
CGFloat width = markdownUtils.markdownStyle.blockquoteBorderWidth;
2846
CGFloat height = rect.size.height;
29-
CGFloat shift = markdownUtils.markdownStyle.blockquoteMarginLeft + markdownUtils.markdownStyle.blockquoteBorderWidth + markdownUtils.markdownStyle.blockquotePaddingLeft;
47+
CGFloat shift = margin + width + padding;
3048
for (int level = 0; level < currentDepth; level++) {
31-
CGFloat x = paddingLeft + (level * shift) + markdownUtils.markdownStyle.blockquoteMarginLeft;
49+
CGFloat x = paddingLeft + (level * shift) + margin;
3250
CGRect lineRect = CGRectMake(x, y, width, height);
33-
[markdownUtils.markdownStyle.blockquoteBorderColor setFill];
51+
[color setFill];
3452
UIRectFill(lineRect);
3553
}
3654
}
3755
}];
3856
}
3957

58+
- (void)drawPreBackgroundForRanges:(NSArray<NSValue*>*)ranges atPoint:(CGPoint)origin withColor:(UIColor*)backgroundColor borderColor:(UIColor*)borderColor borderWidth:(CGFloat)borderWidth borderRadius:(CGFloat)borderRadius andPadding:(CGFloat)padding {
59+
__block CGRect preRect = CGRectNull;
60+
[ranges enumerateObjectsUsingBlock:^(NSValue *item, NSUInteger idx, BOOL * _Nonnull stop) {
61+
NSRange range = [item rangeValue];
62+
// We don't want the trailing ``` to be a part of the block so we need to reduce range by 1.
63+
// This also breaks one character blocks so we need to check if range is larger.
64+
if (range.length > 1) {
65+
range.location += 1;
66+
range.length -= 1;
67+
}
68+
69+
[self enumerateLineFragmentsForGlyphRange:range usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer * _Nonnull textContainer, NSRange glyphRange, BOOL * _Nonnull stop) {
70+
if (CGRectIsNull(preRect)) {
71+
preRect = usedRect;
72+
CGFloat paddingLeft = origin.x;
73+
preRect.origin.x += paddingLeft;
74+
CGFloat paddingTop = origin.y;
75+
preRect.origin.y += paddingTop;
76+
} else {
77+
CGFloat usedWidth = usedRect.size.width;
78+
if (usedWidth > preRect.size.width) {
79+
preRect.size.width = usedWidth;
80+
}
81+
preRect.size.height += usedRect.size.height;
82+
}
83+
}];
84+
85+
if (!CGRectIsNull(preRect)) {
86+
preRect = [self rectByAddingPadding:padding toRect:preRect];
87+
[self drawBackgroundWithColor:backgroundColor borderColor:borderColor borderWidth:borderWidth andBorderRadius:borderRadius forRect:preRect isLeftOpen:NO isRightOpen:NO];
88+
preRect = CGRectNull;
89+
}
90+
}];
91+
}
92+
93+
- (void)drawCodeBackgroundForRanges:(NSArray<NSValue*>*)ranges atPoint:(CGPoint)origin withColor:(UIColor*)backgroundColor borderColor:(UIColor*)borderColor borderWidth:(CGFloat)borderWidth borderRadius:(CGFloat)borderRadius andPadding:(CGFloat)padding {
94+
[ranges enumerateObjectsUsingBlock:^(NSValue *item, NSUInteger idx, BOOL * _Nonnull stop) {
95+
NSRange range = [item rangeValue];
96+
[self enumerateLineFragmentsForGlyphRange:range usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer * _Nonnull textContainer, NSRange glyphRange, BOOL * _Nonnull stop) {
97+
BOOL isLeftSideOpen = YES;
98+
BOOL isRightSideOpen = YES;
99+
100+
NSRange adjustedRange = glyphRange;
101+
if (range.location > adjustedRange.location) {
102+
adjustedRange.length -= range.location - adjustedRange.location;
103+
adjustedRange.location = range.location;
104+
isLeftSideOpen = NO;
105+
}
106+
107+
NSUInteger rangeEndLocation = range.location + range.length;
108+
NSUInteger adjustedRangeEndLocation = adjustedRange.location + adjustedRange.length;
109+
if (rangeEndLocation < adjustedRangeEndLocation) {
110+
adjustedRange.length -= adjustedRangeEndLocation - rangeEndLocation;
111+
isRightSideOpen = NO;
112+
}
113+
114+
CGRect codeRect = [self boundingRectForGlyphRange:adjustedRange inTextContainer:textContainer];
115+
CGFloat paddingLeft = origin.x;
116+
codeRect.origin.x += paddingLeft;
117+
CGFloat paddingTop = origin.y;
118+
codeRect.origin.y += paddingTop;
119+
codeRect = [self rectByAddingPadding:padding toRect:codeRect];
120+
[self drawBackgroundWithColor:backgroundColor borderColor:borderColor borderWidth:borderWidth andBorderRadius:borderRadius forRect:codeRect isLeftOpen:isLeftSideOpen isRightOpen:isRightSideOpen];
121+
}];
122+
}];
123+
}
124+
125+
- (void)drawBackgroundWithColor:(UIColor*)backgroundColor borderColor:(UIColor*)borderColor borderWidth:(CGFloat)borderWidth andBorderRadius:(CGFloat)radius forRect:(CGRect)rect isLeftOpen:(BOOL)isLeftOpen isRightOpen:(BOOL)isRightOpen {
126+
UIRectCorner corners = 0;
127+
if (!isLeftOpen) {
128+
corners |= UIRectCornerTopLeft | UIRectCornerBottomLeft;
129+
}
130+
if (!isRightOpen) {
131+
corners |= UIRectCornerTopRight | UIRectCornerBottomRight;
132+
}
133+
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:CGSizeMake(radius, radius)];
134+
135+
[backgroundColor setFill];
136+
[path fill];
137+
[borderColor setStroke];
138+
[path setLineWidth:borderWidth];
139+
[path stroke];
140+
141+
if (isLeftOpen) {
142+
[self openSideForRect:rect withBorderWidth:borderWidth isLeft:YES];
143+
}
144+
if (isRightOpen) {
145+
[self openSideForRect:rect withBorderWidth:borderWidth isLeft:NO];
146+
}
147+
}
148+
149+
- (void)openSideForRect:(CGRect)rect withBorderWidth:(CGFloat)borderWidth isLeft:(BOOL)isLeft {
150+
UIBezierPath *path = [[UIBezierPath alloc] init];
151+
CGFloat x = isLeft ? CGRectGetMinX(rect) : CGRectGetMaxX(rect);
152+
[path moveToPoint:CGPointMake(x, CGRectGetMinY(rect) - borderWidth)];
153+
[path addLineToPoint:CGPointMake(x, CGRectGetMaxY(rect) + borderWidth)];
154+
[[UIColor clearColor] setStroke];
155+
[path setLineWidth:borderWidth + 1];
156+
[path strokeWithBlendMode:kCGBlendModeClear alpha:1.0];
157+
}
158+
40159
@end

ios/RCTMarkdownStyle.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,18 @@ NS_ASSUME_NONNULL_BEGIN
1818
@property (nonatomic) CGFloat codeFontSize;
1919
@property (nonatomic) UIColor *codeColor;
2020
@property (nonatomic) UIColor *codeBackgroundColor;
21+
@property (nonatomic) UIColor *codeBorderColor;
22+
@property (nonatomic) CGFloat codeBorderWidth;
23+
@property (nonatomic) CGFloat codeBorderRadius;
24+
@property (nonatomic) CGFloat codePadding;
2125
@property (nonatomic) NSString *preFontFamily;
2226
@property (nonatomic) CGFloat preFontSize;
2327
@property (nonatomic) UIColor *preColor;
2428
@property (nonatomic) UIColor *preBackgroundColor;
29+
@property (nonatomic) UIColor *preBorderColor;
30+
@property (nonatomic) CGFloat preBorderWidth;
31+
@property (nonatomic) CGFloat preBorderRadius;
32+
@property (nonatomic) CGFloat prePadding;
2533
@property (nonatomic) UIColor *mentionHereColor;
2634
@property (nonatomic) UIColor *mentionHereBackgroundColor;
2735
@property (nonatomic) UIColor *mentionUserColor;

ios/RCTMarkdownStyle.mm

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,19 @@ - (instancetype)initWithStruct:(const facebook::react::MarkdownTextInputDecorato
3030
_codeFontSize = style.code.fontSize;
3131
_codeColor = RCTUIColorFromSharedColor(style.code.color);
3232
_codeBackgroundColor = RCTUIColorFromSharedColor(style.code.backgroundColor);
33+
_codeBorderColor = RCTUIColorFromSharedColor(style.code.borderColor);
34+
_codeBorderWidth = style.code.borderWidth;
35+
_codeBorderRadius = style.code.borderRadius;
36+
_codePadding = style.code.padding;
3337

3438
_preFontFamily = RCTNSStringFromString(style.pre.fontFamily);
3539
_preFontSize = style.pre.fontSize;
3640
_preColor = RCTUIColorFromSharedColor(style.pre.color);
3741
_preBackgroundColor = RCTUIColorFromSharedColor(style.pre.backgroundColor);
42+
_preBorderColor = RCTUIColorFromSharedColor(style.pre.borderColor);
43+
_preBorderWidth = style.pre.borderWidth;
44+
_preBorderRadius = style.pre.borderRadius;
45+
_prePadding = style.pre.padding;
3846

3947
_mentionHereColor = RCTUIColorFromSharedColor(style.mentionHere.color);
4048
_mentionHereBackgroundColor = RCTUIColorFromSharedColor(style.mentionHere.backgroundColor);
@@ -71,11 +79,19 @@ - (instancetype)initWithDictionary:(NSDictionary *)json
7179
_codeFontSize = [RCTConvert CGFloat:json[@"code"][@"fontSize"]];
7280
_codeColor = [RCTConvert UIColor:json[@"code"][@"color"]];
7381
_codeBackgroundColor = [RCTConvert UIColor:json[@"code"][@"backgroundColor"]];
82+
_codeBorderColor = [RCTConvert UIColor:json[@"code"][@"borderColor"]];
83+
_codeBorderWidth = [RCTConvert CGFloat:json[@"code"][@"borderWidth"]];
84+
_codeBorderRadius = [RCTConvert CGFloat:json[@"code"][@"borderRadius"]];
85+
_codePadding = [RCTConvert CGFloat:json[@"code"][@"padding"]];
7486

7587
_preFontFamily = [RCTConvert NSString:json[@"pre"][@"fontFamily"]];
7688
_preFontSize = [RCTConvert CGFloat:json[@"pre"][@"fontSize"]];
7789
_preColor = [RCTConvert UIColor:json[@"pre"][@"color"]];
7890
_preBackgroundColor = [RCTConvert UIColor:json[@"pre"][@"backgroundColor"]];
91+
_preBorderColor = [RCTConvert UIColor:json[@"pre"][@"borderColor"]];
92+
_preBorderWidth = [RCTConvert CGFloat:json[@"pre"][@"borderWidth"]];
93+
_preBorderRadius = [RCTConvert CGFloat:json[@"pre"][@"borderRadius"]];
94+
_prePadding = [RCTConvert CGFloat:json[@"pre"][@"padding"]];
7995

8096
_mentionHereColor = [RCTConvert UIColor:json[@"mentionHere"][@"color"]];
8197
_mentionHereBackgroundColor = [RCTConvert UIColor:json[@"mentionHere"][@"backgroundColor"]];

ios/RCTMarkdownUtils.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ NS_ASSUME_NONNULL_BEGIN
77

88
@property (nonatomic) RCTMarkdownStyle *markdownStyle;
99
@property (nonatomic) NSMutableArray<NSDictionary *> *blockquoteRangesAndLevels;
10+
@property (nonatomic) NSMutableArray<NSValue *> *codeRanges;
11+
@property (nonatomic) NSMutableArray<NSValue *> *preRanges;
1012

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

0 commit comments

Comments
 (0)