Skip to content

Commit 09f4caf

Browse files
committed
Implement code and pre blocks support on iOS
1 parent 60d75ff commit 09f4caf

7 files changed

+191
-35
lines changed

ios/MarkdownLayoutManager.mm

Lines changed: 145 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,154 @@
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 {
6-
[super drawBackgroundForGlyphRange:glyphsToShow atPoint:origin];
7-
8-
[self enumerateLineFragmentsForGlyphRange:glyphsToShow usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer * _Nonnull textContainer, NSRange glyphRange, BOOL * _Nonnull stop) {
9-
__block BOOL isBlockquote = NO;
10-
__block int currentDepth = 0;
11-
RCTMarkdownUtils *markdownUtils = [self valueForKey:@"markdownUtils"];
12-
[markdownUtils.blockquoteRangesAndLevels enumerateObjectsUsingBlock:^(NSDictionary *item, NSUInteger idx, BOOL * _Nonnull stop) {
13-
NSRange range = [[item valueForKey:@"range"] rangeValue];
14-
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) {
19-
isBlockquote = YES;
20-
*stop = YES;
21-
}
21+
[super drawBackgroundForGlyphRange:glyphsToShow atPoint:origin];
22+
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 {
30+
[self enumerateLineFragmentsForGlyphRange:glyphsToShow usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer * _Nonnull textContainer, NSRange glyphRange, BOOL * _Nonnull stop) {
31+
__block BOOL isBlockquote = NO;
32+
__block int currentDepth = 0;
33+
34+
[ranges enumerateObjectsUsingBlock:^(NSDictionary *item, NSUInteger idx, BOOL * _Nonnull stop) {
35+
NSRange range = [[item valueForKey:@"range"] rangeValue];
36+
currentDepth = [[item valueForKey:@"depth"] unsignedIntegerValue];
37+
if ([self isRange:range inRange:glyphRange]) {
38+
isBlockquote = YES;
39+
*stop = YES;
40+
}
41+
}];
42+
if (isBlockquote) {
43+
CGFloat paddingLeft = origin.x;
44+
CGFloat paddingTop = origin.y;
45+
CGFloat y = paddingTop + rect.origin.y;
46+
CGFloat height = rect.size.height;
47+
CGFloat shift = margin + width + padding;
48+
for (int level = 0; level < currentDepth; level++) {
49+
CGFloat x = paddingLeft + (level * shift) + margin;
50+
CGRect lineRect = CGRectMake(x, y, width, height);
51+
[color setFill];
52+
UIRectFill(lineRect);
53+
}
54+
}
55+
}];
56+
}
57+
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+
range.location += 1;
63+
range.length -= 1;
64+
65+
[self enumerateLineFragmentsForGlyphRange:range usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer * _Nonnull textContainer, NSRange glyphRange, BOOL * _Nonnull stop) {
66+
if (CGRectIsNull(preRect)) {
67+
preRect = usedRect;
68+
CGFloat paddingLeft = origin.x;
69+
preRect.origin.x += paddingLeft;
70+
CGFloat paddingTop = origin.y;
71+
preRect.origin.y += paddingTop;
72+
} else {
73+
CGFloat usedWidth = usedRect.size.width;
74+
if (usedWidth > preRect.size.width) {
75+
preRect.size.width = usedWidth;
76+
}
77+
preRect.size.height += usedRect.size.height;
78+
}
79+
}];
80+
81+
if (!CGRectIsNull(preRect)) {
82+
preRect = [self rectByAddingPadding:padding toRect:preRect];
83+
[self drawBackgroundWithColor:backgroundColor borderColor:borderColor borderWidth:borderWidth andBorderRadius:borderRadius forRect:preRect isLeftOpen:NO isRightOpen:NO];
84+
preRect = CGRectNull;
85+
}
86+
}];
87+
}
88+
89+
- (void)drawCodeBackgroundForRanges:(NSArray<NSValue*>*)ranges atPoint:(CGPoint)origin withColor:(UIColor*)backgroundColor borderColor:(UIColor*)borderColor borderWidth:(CGFloat)borderWidth borderRadius:(CGFloat)borderRadius andPadding:(CGFloat)padding {
90+
[ranges enumerateObjectsUsingBlock:^(NSValue *item, NSUInteger idx, BOOL * _Nonnull stop) {
91+
NSRange range = [item rangeValue];
92+
[self enumerateLineFragmentsForGlyphRange:range usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer * _Nonnull textContainer, NSRange glyphRange, BOOL * _Nonnull stop) {
93+
BOOL isLeftSideOpen = YES;
94+
BOOL isRightSideOpen = YES;
95+
96+
NSRange adjustedRange = glyphRange;
97+
if (range.location > adjustedRange.location) {
98+
adjustedRange.length -= range.location - adjustedRange.location;
99+
adjustedRange.location = range.location;
100+
isLeftSideOpen = NO;
101+
}
102+
103+
NSUInteger rangeEndLocation = range.location + range.length;
104+
NSUInteger adjustedRangeEndLocation = adjustedRange.location + adjustedRange.length;
105+
if (rangeEndLocation < adjustedRangeEndLocation) {
106+
adjustedRange.length -= adjustedRangeEndLocation - rangeEndLocation;
107+
isRightSideOpen = NO;
108+
}
109+
110+
CGRect codeRect = [self boundingRectForGlyphRange:adjustedRange inTextContainer:textContainer];
111+
CGFloat paddingLeft = origin.x;
112+
codeRect.origin.x += paddingLeft;
113+
CGFloat paddingTop = origin.y;
114+
codeRect.origin.y += paddingTop;
115+
codeRect = [self rectByAddingPadding:padding toRect:codeRect];
116+
[self drawBackgroundWithColor:backgroundColor borderColor:borderColor borderWidth:borderWidth andBorderRadius:borderRadius forRect:codeRect isLeftOpen:isLeftSideOpen isRightOpen:isRightSideOpen];
117+
}];
22118
}];
23-
if (isBlockquote) {
24-
CGFloat paddingLeft = origin.x;
25-
CGFloat paddingTop = origin.y;
26-
CGFloat y = paddingTop + rect.origin.y;
27-
CGFloat width = markdownUtils.markdownStyle.blockquoteBorderWidth;
28-
CGFloat height = rect.size.height;
29-
CGFloat shift = markdownUtils.markdownStyle.blockquoteMarginLeft + markdownUtils.markdownStyle.blockquoteBorderWidth + markdownUtils.markdownStyle.blockquotePaddingLeft;
30-
for (int level = 0; level < currentDepth; level++) {
31-
CGFloat x = paddingLeft + (level * shift) + markdownUtils.markdownStyle.blockquoteMarginLeft;
32-
CGRect lineRect = CGRectMake(x, y, width, height);
33-
[markdownUtils.markdownStyle.blockquoteBorderColor setFill];
34-
UIRectFill(lineRect);
35-
}
119+
}
120+
121+
- (void)drawBackgroundWithColor:(UIColor*)backgroundColor borderColor:(UIColor*)borderColor borderWidth:(CGFloat)borderWidth andBorderRadius:(CGFloat)radius forRect:(CGRect)rect isLeftOpen:(BOOL)isLeftOpen isRightOpen:(BOOL)isRightOpen {
122+
UIRectCorner corners = 0;
123+
if (!isLeftOpen) {
124+
corners |= UIRectCornerTopLeft | UIRectCornerBottomLeft;
36125
}
37-
}];
126+
if (!isRightOpen) {
127+
corners |= UIRectCornerTopRight | UIRectCornerBottomRight;
128+
}
129+
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:CGSizeMake(radius, radius)];
130+
131+
[backgroundColor setFill];
132+
[path fill];
133+
[borderColor setStroke];
134+
[path setLineWidth:borderWidth];
135+
[path stroke];
136+
137+
if (isLeftOpen) {
138+
[self openSideForRect:rect withBorderWidth:borderWidth isLeft:YES];
139+
}
140+
if (isRightOpen) {
141+
[self openSideForRect:rect withBorderWidth:borderWidth isLeft:NO];
142+
}
143+
}
144+
145+
- (void)openSideForRect:(CGRect)rect withBorderWidth:(CGFloat)borderWidth isLeft:(BOOL)isLeft {
146+
UIBezierPath *path = [[UIBezierPath alloc] init];
147+
CGFloat x = isLeft ? CGRectGetMinX(rect) : CGRectGetMaxX(rect);
148+
[path moveToPoint:CGPointMake(x, CGRectGetMinY(rect) - borderWidth)];
149+
[path addLineToPoint:CGPointMake(x, CGRectGetMaxY(rect) + borderWidth)];
150+
[[UIColor clearColor] setStroke];
151+
[path setLineWidth:borderWidth + 1];
152+
[path strokeWithBlendMode:kCGBlendModeClear alpha:1.0];
38153
}
39154

40155
@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);
@@ -68,11 +76,19 @@ - (instancetype)initWithDictionary:(NSDictionary *)json
6876
_codeFontSize = [RCTConvert CGFloat:json[@"code"][@"fontSize"]];
6977
_codeColor = [RCTConvert UIColor:json[@"code"][@"color"]];
7078
_codeBackgroundColor = [RCTConvert UIColor:json[@"code"][@"backgroundColor"]];
79+
_codeBorderColor = [RCTConvert UIColor:json[@"code"][@"borderColor"]];
80+
_codeBorderWidth = [RCTConvert CGFloat:json[@"code"][@"borderWidth"]];
81+
_codeBorderRadius = [RCTConvert CGFloat:json[@"code"][@"borderRadius"]];
82+
_codePadding = [RCTConvert CGFloat:json[@"code"][@"padding"]];
7183

7284
_preFontFamily = [RCTConvert NSString:json[@"pre"][@"fontFamily"]];
7385
_preFontSize = [RCTConvert CGFloat:json[@"pre"][@"fontSize"]];
7486
_preColor = [RCTConvert UIColor:json[@"pre"][@"color"]];
7587
_preBackgroundColor = [RCTConvert UIColor:json[@"pre"][@"backgroundColor"]];
88+
_preBorderColor = [RCTConvert UIColor:json[@"pre"][@"borderColor"]];
89+
_preBorderWidth = [RCTConvert CGFloat:json[@"pre"][@"borderWidth"]];
90+
_preBorderRadius = [RCTConvert CGFloat:json[@"pre"][@"borderRadius"]];
91+
_prePadding = [RCTConvert CGFloat:json[@"pre"][@"padding"]];
7692

7793
_mentionHereColor = [RCTConvert UIColor:json[@"mentionHere"][@"color"]];
7894
_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

ios/RCTMarkdownUtils.mm

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ - (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input withA
4747
[attributedString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleNone] range:NSMakeRange(0, attributedString.length)];
4848

4949
_blockquoteRangesAndLevels = [NSMutableArray new];
50+
_codeRanges = [NSMutableArray new];
51+
_preRanges = [NSMutableArray new];
5052

5153
[ranges enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
5254
NSDictionary *item = obj;
@@ -100,7 +102,7 @@ - (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input withA
100102
[attributedString addAttribute:NSStrikethroughStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:range];
101103
} else if ([type isEqualToString:@"code"]) {
102104
[attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.codeColor range:range];
103-
[attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.codeBackgroundColor range:range];
105+
[_codeRanges addObject:[NSValue valueWithRange:range]];
104106
} else if ([type isEqualToString:@"mention-here"]) {
105107
[attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.mentionHereColor range:range];
106108
[attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.mentionHereBackgroundColor range:range];
@@ -123,9 +125,7 @@ - (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input withA
123125
}];
124126
} else if ([type isEqualToString:@"pre"]) {
125127
[attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.preColor range:range];
126-
NSRange rangeForBackground = [inputString characterAtIndex:range.location] == '\n' ? NSMakeRange(range.location + 1, range.length - 1) : range;
127-
[attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.preBackgroundColor range:rangeForBackground];
128-
// TODO: pass background color and ranges to layout manager
128+
[_preRanges addObject:[NSValue valueWithRange:range]];
129129
} else if ([type isEqualToString:@"h1"]) {
130130
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
131131
NSRange rangeWithHashAndSpace = NSMakeRange(range.location - 2, range.length + 2); // we also need to include prepending "# "
@@ -141,7 +141,6 @@ - (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input withA
141141
_prevMarkdownStyle = _markdownStyle;
142142

143143
return attributedString;
144-
145144
}
146145
}
147146

src/MarkdownTextInputDecoratorViewNativeComponent.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,20 @@ interface MarkdownStyle {
2727
fontSize: Float;
2828
color: ColorValue;
2929
backgroundColor: ColorValue;
30+
borderColor: ColorValue;
31+
borderWidth: Float;
32+
borderRadius: Float;
33+
padding: Float;
3034
};
3135
pre: {
3236
fontFamily: string;
3337
fontSize: Float;
3438
color: ColorValue;
3539
backgroundColor: ColorValue;
40+
borderColor: ColorValue;
41+
borderWidth: Float;
42+
borderRadius: Float;
43+
padding: Float;
3644
};
3745
mentionHere: {
3846
color: ColorValue;

src/styleUtils.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,20 @@ function makeDefaultMarkdownStyle(): MarkdownStyle {
3737
fontSize: 20,
3838
color: 'black',
3939
backgroundColor: 'lightgray',
40+
borderColor: 'gray',
41+
borderWidth: 1,
42+
borderRadius: 4,
43+
padding: 0,
4044
},
4145
pre: {
4246
fontFamily: FONT_FAMILY_MONOSPACE,
4347
fontSize: 20,
4448
color: 'black',
4549
backgroundColor: 'lightgray',
50+
borderColor: 'gray',
51+
borderWidth: 1,
52+
borderRadius: 4,
53+
padding: 2,
4654
},
4755
mentionHere: {
4856
color: 'green',

0 commit comments

Comments
 (0)