-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBlockquoteRenderer.m
More file actions
160 lines (133 loc) · 5.87 KB
/
BlockquoteRenderer.m
File metadata and controls
160 lines (133 loc) · 5.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#import "BlockquoteRenderer.h"
#import "BlockquoteBorder.h"
#import "FontUtils.h"
#import "MarkdownASTNode.h"
#import "ParagraphStyleUtils.h"
#import "RendererFactory.h"
#import "StyleConfig.h"
static NSString *const kNestedInfoDepthKey = @"depth";
static NSString *const kNestedInfoRangeKey = @"range";
@implementation BlockquoteRenderer {
__weak RendererFactory *_rendererFactory;
StyleConfig *_config;
}
- (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
{
if (self = [super init]) {
_rendererFactory = rendererFactory;
_config = (StyleConfig *)config;
}
return self;
}
- (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
{
NSInteger currentDepth = context.blockquoteDepth;
context.blockquoteDepth = currentDepth + 1;
[context setBlockStyle:BlockTypeBlockquote
font:[_config blockquoteFont]
color:[_config blockquoteColor]
headingLevel:0];
NSUInteger start = output.length;
@try {
[_rendererFactory renderChildrenOfNode:node into:output context:context];
} @finally {
[context clearBlockStyle];
context.blockquoteDepth = currentDepth;
}
NSUInteger end = output.length;
if (end <= start) {
return;
}
[self applyStylingAndSpacing:output start:start end:end currentDepth:currentDepth];
}
#pragma mark - Styling and Spacing
- (void)applyStylingAndSpacing:(NSMutableAttributedString *)output
start:(NSUInteger)start
end:(NSUInteger)end
currentDepth:(NSInteger)currentDepth
{
NSUInteger contentStart = start;
if (currentDepth == 0) {
contentStart += applyBlockSpacingBefore(output, start, [_config blockquoteMarginTop]);
}
NSRange blockquoteRange = NSMakeRange(contentStart, end - start);
CGFloat levelSpacing = [_config blockquoteBorderWidth] + [_config blockquoteGapWidth];
NSArray<NSDictionary *> *nestedInfo = [self collectNestedBlockquotes:output range:blockquoteRange depth:currentDepth];
// Apply base styling (indentation, depth, background, line height)
[self applyBaseBlockquoteStyle:output
range:blockquoteRange
depth:currentDepth
levelSpacing:levelSpacing
backgroundColor:[_config blockquoteBackgroundColor]
lineHeight:[_config blockquoteLineHeight]];
// Re-apply nested blockquote styles to restore their correct indentation
// (applyBaseBlockquoteStyle overwrites nested indents with the parent's indent)
[self reapplyNestedStyles:output nestedInfo:nestedInfo levelSpacing:levelSpacing];
if (currentDepth == 0) {
applyBlockSpacingAfter(output, [_config blockquoteMarginBottom]);
}
}
#pragma mark - Nested Blockquote Handling
- (NSArray<NSDictionary *> *)collectNestedBlockquotes:(NSMutableAttributedString *)output
range:(NSRange)blockquoteRange
depth:(NSInteger)currentDepth
{
NSMutableArray<NSDictionary *> *nestedInfo = [NSMutableArray array];
[output
enumerateAttribute:BlockquoteDepthAttributeName
inRange:blockquoteRange
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
usingBlock:^(id value, NSRange range, BOOL *stop) {
NSInteger depth = [value integerValue];
if (value && depth > currentDepth) {
[nestedInfo
addObject:@{kNestedInfoDepthKey : value, kNestedInfoRangeKey : [NSValue valueWithRange:range]}];
}
}];
return nestedInfo;
}
- (void)applyBaseBlockquoteStyle:(NSMutableAttributedString *)output
range:(NSRange)blockquoteRange
depth:(NSInteger)currentDepth
levelSpacing:(CGFloat)levelSpacing
backgroundColor:(RCTUIColor *)backgroundColor
lineHeight:(CGFloat)lineHeight
{
NSMutableParagraphStyle *paragraphStyle = getOrCreateParagraphStyle(output, blockquoteRange.location);
CGFloat totalIndent = [self calculateIndentForDepth:currentDepth levelSpacing:levelSpacing];
paragraphStyle.firstLineHeadIndent = totalIndent;
paragraphStyle.headIndent = totalIndent;
NSMutableDictionary *newAttributes =
[NSMutableDictionary dictionaryWithObjectsAndKeys:paragraphStyle, NSParagraphStyleAttributeName, @(currentDepth),
BlockquoteDepthAttributeName, nil];
if (backgroundColor) {
newAttributes[BlockquoteBackgroundColorAttributeName] = backgroundColor;
}
[output addAttributes:newAttributes range:blockquoteRange];
applyLineHeight(output, blockquoteRange, lineHeight);
}
- (void)reapplyNestedStyles:(NSMutableAttributedString *)output
nestedInfo:(NSArray<NSDictionary *> *)nestedInfo
levelSpacing:(CGFloat)levelSpacing
{
// Re-apply indentation to nested blockquotes since applyBaseBlockquoteStyle
// overwrote them with the parent's indentation
for (NSDictionary *info in nestedInfo) {
NSRange nestedRange = [info[kNestedInfoRangeKey] rangeValue];
NSInteger nestedDepth = [info[kNestedInfoDepthKey] integerValue];
NSMutableParagraphStyle *style = getOrCreateParagraphStyle(output, nestedRange.location);
CGFloat indent = [self calculateIndentForDepth:nestedDepth levelSpacing:levelSpacing];
style.firstLineHeadIndent = indent;
style.headIndent = indent;
style.tailIndent = 0;
[output
addAttributes:@{NSParagraphStyleAttributeName : style, BlockquoteDepthAttributeName : info[kNestedInfoDepthKey]}
range:nestedRange];
}
}
#pragma mark - Helper Methods
- (CGFloat)calculateIndentForDepth:(NSInteger)depth levelSpacing:(CGFloat)levelSpacing
{
return (depth + 1) * levelSpacing;
}
@end