react-native-enriched-markdown 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +479 -0
- package/ReactNativeEnrichedMarkdown.podspec +27 -0
- package/android/build.gradle +101 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +39 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +21 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp +22 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.h +24 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +24 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +25 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +57 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +1164 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.cpp +17 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.h +32 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.cpp +16 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.h +20 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/baseline-prof.txt +65 -0
- package/android/src/main/cpp/jni-adapter.cpp +203 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +153 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextLayoutManager.kt +30 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +119 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextPackage.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +165 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkPressEvent.kt +23 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/parser/MarkdownASTNode.kt +29 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/parser/Parser.kt +48 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockStyleContext.kt +166 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt +89 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeBlockRenderer.kt +105 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeRenderer.kt +35 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/DocumentRenderer.kt +15 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/EmphasisRenderer.kt +26 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/HeadingRenderer.kt +54 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ImageRenderer.kt +52 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LineBreakRenderer.kt +15 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +28 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListContextManager.kt +105 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListItemRenderer.kt +58 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListRenderer.kt +69 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/NodeRenderer.kt +99 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ParagraphRenderer.kt +66 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/Renderer.kt +95 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +85 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrongRenderer.kt +26 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/TextRenderer.kt +29 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ThematicBreakRenderer.kt +44 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/BaseListSpan.kt +136 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/BlockquoteSpan.kt +135 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +180 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBlockSpan.kt +196 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeSpan.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/EmphasisSpan.kt +34 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/HeadingSpan.kt +38 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/ImageSpan.kt +320 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LineHeightSpan.kt +36 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +37 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/MarginBottomSpan.kt +76 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/OrderedListSpan.kt +87 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/StrongSpan.kt +37 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/TextSpan.kt +26 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/ThematicBreakSpan.kt +69 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/UnorderedListSpan.kt +69 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/BaseBlockStyle.kt +10 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/BlockquoteStyle.kt +48 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeBlockStyle.kt +51 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeStyle.kt +21 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/EmphasisStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/HeadingStyle.kt +29 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ImageStyle.kt +21 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/InlineImageStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkStyle.kt +19 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt +54 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ParagraphStyle.kt +29 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StrongStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt +180 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleParser.kt +75 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ThematicBreakStyle.kt +23 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/AsyncDrawable.kt +91 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/HTMLGenerator.kt +809 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/MarkdownExtractor.kt +365 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/SelectionActionMode.kt +139 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/Utils.kt +181 -0
- package/android/src/main/jni/CMakeLists.txt +82 -0
- package/android/src/main/jni/EnrichedMarkdownTextSpec.cpp +21 -0
- package/android/src/main/jni/EnrichedMarkdownTextSpec.h +25 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextComponentDescriptor.h +29 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextMeasurementManager.cpp +45 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextMeasurementManager.h +21 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.cpp +33 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.h +49 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.cpp +9 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.h +25 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/conversions.h +19 -0
- package/cpp/md4c/md4c.c +6492 -0
- package/cpp/md4c/md4c.h +402 -0
- package/cpp/parser/MD4CParser.cpp +314 -0
- package/cpp/parser/MD4CParser.hpp +23 -0
- package/cpp/parser/MarkdownASTNode.hpp +49 -0
- package/ios/EnrichedMarkdownText.h +18 -0
- package/ios/EnrichedMarkdownText.mm +1074 -0
- package/ios/attachments/ImageAttachment.h +23 -0
- package/ios/attachments/ImageAttachment.m +185 -0
- package/ios/attachments/ThematicBreakAttachment.h +15 -0
- package/ios/attachments/ThematicBreakAttachment.m +33 -0
- package/ios/generated/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp +22 -0
- package/ios/generated/EnrichedMarkdownTextSpec/ComponentDescriptors.h +24 -0
- package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.cpp +24 -0
- package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.h +25 -0
- package/ios/generated/EnrichedMarkdownTextSpec/Props.cpp +57 -0
- package/ios/generated/EnrichedMarkdownTextSpec/Props.h +1164 -0
- package/ios/generated/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +20 -0
- package/ios/generated/EnrichedMarkdownTextSpec/ShadowNodes.cpp +17 -0
- package/ios/generated/EnrichedMarkdownTextSpec/ShadowNodes.h +32 -0
- package/ios/generated/EnrichedMarkdownTextSpec/States.cpp +16 -0
- package/ios/generated/EnrichedMarkdownTextSpec/States.h +20 -0
- package/ios/internals/EnrichedMarkdownTextComponentDescriptor.h +19 -0
- package/ios/internals/EnrichedMarkdownTextShadowNode.h +43 -0
- package/ios/internals/EnrichedMarkdownTextShadowNode.mm +85 -0
- package/ios/internals/EnrichedMarkdownTextState.h +24 -0
- package/ios/parser/MarkdownASTNode.h +33 -0
- package/ios/parser/MarkdownASTNode.m +32 -0
- package/ios/parser/MarkdownParser.h +8 -0
- package/ios/parser/MarkdownParser.mm +13 -0
- package/ios/parser/MarkdownParserBridge.mm +110 -0
- package/ios/renderer/AttributedRenderer.h +9 -0
- package/ios/renderer/AttributedRenderer.m +119 -0
- package/ios/renderer/BlockquoteRenderer.h +7 -0
- package/ios/renderer/BlockquoteRenderer.m +159 -0
- package/ios/renderer/CodeBlockRenderer.h +10 -0
- package/ios/renderer/CodeBlockRenderer.m +89 -0
- package/ios/renderer/CodeRenderer.h +10 -0
- package/ios/renderer/CodeRenderer.m +60 -0
- package/ios/renderer/EmphasisRenderer.h +6 -0
- package/ios/renderer/EmphasisRenderer.m +96 -0
- package/ios/renderer/HeadingRenderer.h +7 -0
- package/ios/renderer/HeadingRenderer.m +98 -0
- package/ios/renderer/ImageRenderer.h +12 -0
- package/ios/renderer/ImageRenderer.m +62 -0
- package/ios/renderer/LinkRenderer.h +7 -0
- package/ios/renderer/LinkRenderer.m +69 -0
- package/ios/renderer/ListItemRenderer.h +16 -0
- package/ios/renderer/ListItemRenderer.m +91 -0
- package/ios/renderer/ListRenderer.h +13 -0
- package/ios/renderer/ListRenderer.m +67 -0
- package/ios/renderer/NodeRenderer.h +8 -0
- package/ios/renderer/ParagraphRenderer.h +7 -0
- package/ios/renderer/ParagraphRenderer.m +69 -0
- package/ios/renderer/RenderContext.h +88 -0
- package/ios/renderer/RenderContext.m +248 -0
- package/ios/renderer/RendererFactory.h +12 -0
- package/ios/renderer/RendererFactory.m +110 -0
- package/ios/renderer/StrongRenderer.h +6 -0
- package/ios/renderer/StrongRenderer.m +83 -0
- package/ios/renderer/TextRenderer.h +6 -0
- package/ios/renderer/TextRenderer.m +16 -0
- package/ios/renderer/ThematicBreakRenderer.h +5 -0
- package/ios/renderer/ThematicBreakRenderer.m +53 -0
- package/ios/styles/StyleConfig.h +228 -0
- package/ios/styles/StyleConfig.mm +1467 -0
- package/ios/utils/BlockquoteBorder.h +20 -0
- package/ios/utils/BlockquoteBorder.m +92 -0
- package/ios/utils/CodeBackground.h +19 -0
- package/ios/utils/CodeBackground.m +191 -0
- package/ios/utils/CodeBlockBackground.h +17 -0
- package/ios/utils/CodeBlockBackground.m +87 -0
- package/ios/utils/EditMenuUtils.h +22 -0
- package/ios/utils/EditMenuUtils.m +118 -0
- package/ios/utils/FontUtils.h +20 -0
- package/ios/utils/FontUtils.m +13 -0
- package/ios/utils/HTMLGenerator.h +20 -0
- package/ios/utils/HTMLGenerator.m +779 -0
- package/ios/utils/LastElementUtils.h +53 -0
- package/ios/utils/ListMarkerDrawer.h +15 -0
- package/ios/utils/ListMarkerDrawer.m +127 -0
- package/ios/utils/MarkdownExtractor.h +17 -0
- package/ios/utils/MarkdownExtractor.m +295 -0
- package/ios/utils/ParagraphStyleUtils.h +13 -0
- package/ios/utils/ParagraphStyleUtils.m +56 -0
- package/ios/utils/PasteboardUtils.h +36 -0
- package/ios/utils/PasteboardUtils.m +134 -0
- package/ios/utils/RTFExportUtils.h +24 -0
- package/ios/utils/RTFExportUtils.m +297 -0
- package/ios/utils/RuntimeKeys.h +38 -0
- package/ios/utils/RuntimeKeys.m +11 -0
- package/ios/utils/TextViewLayoutManager.h +14 -0
- package/ios/utils/TextViewLayoutManager.mm +113 -0
- package/lib/module/EnrichedMarkdownText.js +34 -0
- package/lib/module/EnrichedMarkdownText.js.map +1 -0
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +130 -0
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/normalizeMarkdownStyle.js +340 -0
- package/lib/module/normalizeMarkdownStyle.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/EnrichedMarkdownText.d.ts +101 -0
- package/lib/typescript/src/EnrichedMarkdownText.d.ts.map +1 -0
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +111 -0
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +5 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/normalizeMarkdownStyle.d.ts +6 -0
- package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -0
- package/package.json +186 -1
- package/react-native.config.js +13 -0
- package/src/EnrichedMarkdownText.tsx +152 -0
- package/src/EnrichedMarkdownTextNativeComponent.ts +130 -0
- package/src/index.tsx +7 -0
- package/src/normalizeMarkdownStyle.ts +377 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#import "LinkRenderer.h"
|
|
2
|
+
#import "FontUtils.h"
|
|
3
|
+
#import "RenderContext.h"
|
|
4
|
+
#import "RendererFactory.h"
|
|
5
|
+
#import "StyleConfig.h"
|
|
6
|
+
|
|
7
|
+
@implementation LinkRenderer {
|
|
8
|
+
RendererFactory *_rendererFactory;
|
|
9
|
+
StyleConfig *_config;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
- (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
|
|
13
|
+
{
|
|
14
|
+
self = [super init];
|
|
15
|
+
if (self) {
|
|
16
|
+
_rendererFactory = rendererFactory;
|
|
17
|
+
_config = (StyleConfig *)config;
|
|
18
|
+
}
|
|
19
|
+
return self;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#pragma mark - Rendering
|
|
23
|
+
|
|
24
|
+
- (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
|
|
25
|
+
{
|
|
26
|
+
NSUInteger start = output.length;
|
|
27
|
+
|
|
28
|
+
// 1. Render children first to establish base attributes
|
|
29
|
+
[_rendererFactory renderChildrenOfNode:node into:output context:context];
|
|
30
|
+
|
|
31
|
+
NSRange range = NSMakeRange(start, output.length - start);
|
|
32
|
+
if (range.length == 0)
|
|
33
|
+
return;
|
|
34
|
+
|
|
35
|
+
// 2. Extract configuration
|
|
36
|
+
NSString *url = node.attributes[@"url"] ?: @"";
|
|
37
|
+
UIColor *linkColor = [_config linkColor];
|
|
38
|
+
NSNumber *underlineStyle = @([_config linkUnderline] ? NSUnderlineStyleSingle : NSUnderlineStyleNone);
|
|
39
|
+
|
|
40
|
+
// 3. Apply core link functionality (non-destructive)
|
|
41
|
+
[output addAttribute:NSLinkAttributeName value:url range:range];
|
|
42
|
+
|
|
43
|
+
// 4. Optimize visual attributes via enumeration to avoid redundant updates
|
|
44
|
+
[output enumerateAttributesInRange:range
|
|
45
|
+
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
|
|
46
|
+
usingBlock:^(NSDictionary<NSAttributedStringKey, id> *attrs, NSRange subrange, BOOL *stop) {
|
|
47
|
+
NSMutableDictionary *newAttributes = [NSMutableDictionary dictionary];
|
|
48
|
+
|
|
49
|
+
// Only apply link color if the subrange isn't already colored by the link style
|
|
50
|
+
if (linkColor && ![attrs[NSForegroundColorAttributeName] isEqual:linkColor]) {
|
|
51
|
+
newAttributes[NSForegroundColorAttributeName] = linkColor;
|
|
52
|
+
newAttributes[NSUnderlineColorAttributeName] = linkColor;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Only update underline style if it differs from the config
|
|
56
|
+
if (![attrs[NSUnderlineStyleAttributeName] isEqual:underlineStyle]) {
|
|
57
|
+
newAttributes[NSUnderlineStyleAttributeName] = underlineStyle;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (newAttributes.count > 0) {
|
|
61
|
+
[output addAttributes:newAttributes range:subrange];
|
|
62
|
+
}
|
|
63
|
+
}];
|
|
64
|
+
|
|
65
|
+
// 5. Register for touch handling
|
|
66
|
+
[context registerLinkRange:range url:url];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#import "NodeRenderer.h"
|
|
2
|
+
|
|
3
|
+
@class RendererFactory;
|
|
4
|
+
@class StyleConfig;
|
|
5
|
+
@class RenderContext;
|
|
6
|
+
|
|
7
|
+
// Attribute names for list styling
|
|
8
|
+
extern NSString *const ListDepthAttribute;
|
|
9
|
+
extern NSString *const ListTypeAttribute;
|
|
10
|
+
extern NSString *const ListItemNumberAttribute;
|
|
11
|
+
|
|
12
|
+
@interface ListItemRenderer : NSObject <NodeRenderer>
|
|
13
|
+
|
|
14
|
+
- (instancetype)initWithRendererFactory:(RendererFactory *)rendererFactory config:(StyleConfig *)config;
|
|
15
|
+
|
|
16
|
+
@end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#import "ListItemRenderer.h"
|
|
2
|
+
#import "MarkdownASTNode.h"
|
|
3
|
+
#import "ParagraphStyleUtils.h"
|
|
4
|
+
#import "RenderContext.h"
|
|
5
|
+
#import "RendererFactory.h"
|
|
6
|
+
#import "StyleConfig.h"
|
|
7
|
+
|
|
8
|
+
NSString *const ListDepthAttribute = @"ListDepth";
|
|
9
|
+
NSString *const ListTypeAttribute = @"ListType";
|
|
10
|
+
NSString *const ListItemNumberAttribute = @"ListItemNumber";
|
|
11
|
+
|
|
12
|
+
@implementation ListItemRenderer {
|
|
13
|
+
RendererFactory *_rendererFactory;
|
|
14
|
+
StyleConfig *_config;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
- (instancetype)initWithRendererFactory:(RendererFactory *)factory config:(StyleConfig *)config
|
|
18
|
+
{
|
|
19
|
+
if (self = [super init]) {
|
|
20
|
+
_rendererFactory = factory;
|
|
21
|
+
_config = config;
|
|
22
|
+
}
|
|
23
|
+
return self;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
- (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
|
|
27
|
+
{
|
|
28
|
+
if (!context)
|
|
29
|
+
return;
|
|
30
|
+
|
|
31
|
+
// 1. Maintain the ordered list counter
|
|
32
|
+
if (context.listType == ListTypeOrdered) {
|
|
33
|
+
context.listItemNumber++;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
NSUInteger start = output.length;
|
|
37
|
+
[_rendererFactory renderChildrenOfNode:node into:output context:context];
|
|
38
|
+
|
|
39
|
+
// 2. Structural Fix: Ensure paragraph isolation to prevent merged lines
|
|
40
|
+
if (output.length > start && ![output.string hasSuffix:@"\n"]) {
|
|
41
|
+
[output appendAttributedString:kNewlineAttributedString];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
NSRange itemRange = NSMakeRange(start, output.length - start);
|
|
45
|
+
if (itemRange.length == 0)
|
|
46
|
+
return;
|
|
47
|
+
|
|
48
|
+
// 3. Pre-calculate invariant metadata for this item
|
|
49
|
+
NSInteger currentDepth = context.listDepth - 1;
|
|
50
|
+
// Root items: just marker width + gap (flush to left edge)
|
|
51
|
+
// Nested items: add marginLeft for each nesting level
|
|
52
|
+
CGFloat minMarkerWidth = (context.listType == ListTypeOrdered) ? [_config effectiveListMarginLeftForNumber]
|
|
53
|
+
: [_config effectiveListMarginLeftForBullet];
|
|
54
|
+
CGFloat indent = minMarkerWidth + [_config effectiveListGapWidth] + (currentDepth * [_config listStyleMarginLeft]);
|
|
55
|
+
CGFloat configLineHeight = [_config listStyleLineHeight];
|
|
56
|
+
|
|
57
|
+
// Pre-wrap numbers to avoid repeated allocations in the block
|
|
58
|
+
NSNumber *depthVal = @(currentDepth);
|
|
59
|
+
NSNumber *typeVal = @(context.listType);
|
|
60
|
+
NSNumber *numVal = @(context.listItemNumber);
|
|
61
|
+
|
|
62
|
+
// 4. Protected Styling: Use enumerateAttribute to avoid flattening children
|
|
63
|
+
[output enumerateAttribute:ListDepthAttribute
|
|
64
|
+
inRange:itemRange
|
|
65
|
+
options:0
|
|
66
|
+
usingBlock:^(id depth, NSRange range, BOOL *stop) {
|
|
67
|
+
// Skip if this segment belongs to a deeper nested list
|
|
68
|
+
if (depth && [depth integerValue] > currentDepth)
|
|
69
|
+
return;
|
|
70
|
+
|
|
71
|
+
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
|
|
72
|
+
style.firstLineHeadIndent = indent;
|
|
73
|
+
style.headIndent = indent;
|
|
74
|
+
|
|
75
|
+
UIFont *font = [output attribute:NSFontAttributeName atIndex:range.location effectiveRange:NULL];
|
|
76
|
+
if (configLineHeight > 0 && font) {
|
|
77
|
+
style.lineHeightMultiple = configLineHeight / font.pointSize;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Final attribute application
|
|
81
|
+
[output addAttributes:@{
|
|
82
|
+
NSParagraphStyleAttributeName : style,
|
|
83
|
+
ListDepthAttribute : depthVal,
|
|
84
|
+
ListTypeAttribute : typeVal,
|
|
85
|
+
ListItemNumberAttribute : numVal
|
|
86
|
+
}
|
|
87
|
+
range:range];
|
|
88
|
+
}];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#import "NodeRenderer.h"
|
|
2
|
+
|
|
3
|
+
@class RendererFactory;
|
|
4
|
+
@class StyleConfig;
|
|
5
|
+
@class RenderContext;
|
|
6
|
+
|
|
7
|
+
@interface ListRenderer : NSObject <NodeRenderer>
|
|
8
|
+
|
|
9
|
+
- (instancetype)initWithRendererFactory:(RendererFactory *)rendererFactory
|
|
10
|
+
config:(StyleConfig *)config
|
|
11
|
+
isOrdered:(BOOL)isOrdered;
|
|
12
|
+
|
|
13
|
+
@end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#import "ListRenderer.h"
|
|
2
|
+
#import "BlockquoteRenderer.h"
|
|
3
|
+
#import "MarkdownASTNode.h"
|
|
4
|
+
#import "ParagraphStyleUtils.h"
|
|
5
|
+
#import "RenderContext.h"
|
|
6
|
+
#import "RendererFactory.h"
|
|
7
|
+
#import "StyleConfig.h"
|
|
8
|
+
|
|
9
|
+
@implementation ListRenderer {
|
|
10
|
+
RendererFactory *_rendererFactory;
|
|
11
|
+
StyleConfig *_config;
|
|
12
|
+
BOOL _isOrdered;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
- (instancetype)initWithRendererFactory:(RendererFactory *)factory config:(StyleConfig *)config isOrdered:(BOOL)ordered
|
|
16
|
+
{
|
|
17
|
+
if (self = [super init]) {
|
|
18
|
+
_rendererFactory = factory;
|
|
19
|
+
_config = config;
|
|
20
|
+
_isOrdered = ordered;
|
|
21
|
+
}
|
|
22
|
+
return self;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
- (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
|
|
26
|
+
{
|
|
27
|
+
if (!context)
|
|
28
|
+
return;
|
|
29
|
+
|
|
30
|
+
// Snapshot parent state
|
|
31
|
+
NSInteger prevDepth = context.listDepth;
|
|
32
|
+
ListType prevType = context.listType;
|
|
33
|
+
NSInteger prevNum = context.listItemNumber;
|
|
34
|
+
|
|
35
|
+
// Configure depth and type
|
|
36
|
+
context.listDepth = prevDepth + 1;
|
|
37
|
+
context.listType = _isOrdered ? ListTypeOrdered : ListTypeUnordered;
|
|
38
|
+
context.listItemNumber = 0;
|
|
39
|
+
|
|
40
|
+
// Ensure isolation for nested lists
|
|
41
|
+
if (prevDepth > 0 && output.length > 0 && ![output.string hasSuffix:@"\n"]) {
|
|
42
|
+
[output appendAttributedString:kNewlineAttributedString];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
[context setBlockStyle:_isOrdered ? BlockTypeOrderedList : BlockTypeUnorderedList
|
|
46
|
+
font:[_config listStyleFont]
|
|
47
|
+
color:[_config listStyleColor]
|
|
48
|
+
headingLevel:0];
|
|
49
|
+
|
|
50
|
+
@try {
|
|
51
|
+
[_rendererFactory renderChildrenOfNode:node into:output context:context];
|
|
52
|
+
} @finally {
|
|
53
|
+
// Restore parent state
|
|
54
|
+
context.listDepth = prevDepth;
|
|
55
|
+
context.listType = prevType;
|
|
56
|
+
context.listItemNumber = prevNum;
|
|
57
|
+
if (prevDepth == 0)
|
|
58
|
+
[context clearBlockStyle];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Final spacing for root container
|
|
62
|
+
if (prevDepth == 0 && [_config listStyleMarginBottom] > 0) {
|
|
63
|
+
applyBlockSpacing(output, [_config listStyleMarginBottom]);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#import "ParagraphRenderer.h"
|
|
2
|
+
#import "FontUtils.h"
|
|
3
|
+
#import "MarkdownASTNode.h"
|
|
4
|
+
#import "ParagraphStyleUtils.h"
|
|
5
|
+
#import "RendererFactory.h"
|
|
6
|
+
#import "StyleConfig.h"
|
|
7
|
+
|
|
8
|
+
@implementation ParagraphRenderer {
|
|
9
|
+
RendererFactory *_rendererFactory;
|
|
10
|
+
StyleConfig *_config;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
- (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
|
|
14
|
+
{
|
|
15
|
+
self = [super init];
|
|
16
|
+
if (self) {
|
|
17
|
+
_rendererFactory = rendererFactory;
|
|
18
|
+
_config = (StyleConfig *)config;
|
|
19
|
+
}
|
|
20
|
+
return self;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#pragma mark - Rendering
|
|
24
|
+
|
|
25
|
+
- (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
|
|
26
|
+
{
|
|
27
|
+
// 1. Context-Aware Styling
|
|
28
|
+
// We only set the block style if no parent element (like a list or blockquote) has already established one.
|
|
29
|
+
BOOL isTopLevel = (context.currentBlockType == BlockTypeNone);
|
|
30
|
+
|
|
31
|
+
if (isTopLevel) {
|
|
32
|
+
[context setBlockStyle:BlockTypeParagraph font:_config.paragraphFont color:_config.paragraphColor headingLevel:0];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
NSUInteger start = output.length;
|
|
36
|
+
@try {
|
|
37
|
+
[_rendererFactory renderChildrenOfNode:node into:output context:context];
|
|
38
|
+
} @finally {
|
|
39
|
+
// Only clear the style if this paragraph was the one that set it.
|
|
40
|
+
if (isTopLevel) {
|
|
41
|
+
[context clearBlockStyle];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 2. Geometry and Spacing Logic
|
|
46
|
+
if (output.length <= start)
|
|
47
|
+
return;
|
|
48
|
+
NSRange range = NSMakeRange(start, output.length - start);
|
|
49
|
+
|
|
50
|
+
// Check if this paragraph is purely a wrapper for a block image.
|
|
51
|
+
// Images often require different spacing and should not have standard line height applied.
|
|
52
|
+
BOOL isBlockImage = (node.children.count == 1 && ((MarkdownASTNode *)node.children[0]).type == MarkdownNodeTypeImage);
|
|
53
|
+
|
|
54
|
+
// Apply line height only for text paragraphs to avoid unwanted gaps above/below images.
|
|
55
|
+
if (!isBlockImage) {
|
|
56
|
+
applyLineHeight(output, range, _config.paragraphLineHeight);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 3. Margin Application
|
|
60
|
+
// Only top-level paragraphs apply bottom margins; nested paragraphs defer to their parents.
|
|
61
|
+
CGFloat marginBottom = 0;
|
|
62
|
+
if (isTopLevel) {
|
|
63
|
+
marginBottom = isBlockImage ? _config.imageMarginBottom : _config.paragraphMarginBottom;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
applyParagraphSpacing(output, start, marginBottom);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
#import <UIKit/UIKit.h>
|
|
3
|
+
|
|
4
|
+
typedef NS_ENUM(NSInteger, BlockType) {
|
|
5
|
+
BlockTypeNone,
|
|
6
|
+
BlockTypeParagraph,
|
|
7
|
+
BlockTypeHeading,
|
|
8
|
+
BlockTypeBlockquote,
|
|
9
|
+
BlockTypeUnorderedList,
|
|
10
|
+
BlockTypeOrderedList,
|
|
11
|
+
BlockTypeCodeBlock
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
typedef NS_ENUM(NSInteger, ListType) { ListTypeUnordered, ListTypeOrdered };
|
|
15
|
+
|
|
16
|
+
@interface BlockStyle : NSObject
|
|
17
|
+
@property (nonatomic, assign) CGFloat fontSize;
|
|
18
|
+
@property (nonatomic, strong) NSString *fontFamily;
|
|
19
|
+
@property (nonatomic, strong) NSString *fontWeight;
|
|
20
|
+
@property (nonatomic, strong) UIColor *color;
|
|
21
|
+
@property (nonatomic, strong) UIFont *cachedFont;
|
|
22
|
+
@property (nonatomic, strong) NSDictionary *cachedTextAttributes;
|
|
23
|
+
@end
|
|
24
|
+
|
|
25
|
+
@interface RenderContext : NSObject
|
|
26
|
+
@property (nonatomic, strong) NSMutableArray<NSValue *> *linkRanges;
|
|
27
|
+
@property (nonatomic, strong) NSMutableArray<NSString *> *linkURLs;
|
|
28
|
+
@property (nonatomic, assign) BlockType currentBlockType;
|
|
29
|
+
@property (nonatomic, strong) BlockStyle *currentBlockStyle;
|
|
30
|
+
@property (nonatomic, assign) NSInteger currentHeadingLevel;
|
|
31
|
+
@property (nonatomic, assign) NSInteger blockquoteDepth;
|
|
32
|
+
@property (nonatomic, assign) NSInteger listDepth;
|
|
33
|
+
@property (nonatomic, assign) ListType listType;
|
|
34
|
+
@property (nonatomic, assign) NSInteger listItemNumber;
|
|
35
|
+
|
|
36
|
+
- (instancetype)init;
|
|
37
|
+
- (void)reset;
|
|
38
|
+
|
|
39
|
+
- (UIFont *)cachedFontForSize:(CGFloat)fontSize family:(NSString *)fontFamily weight:(NSString *)fontWeight;
|
|
40
|
+
- (NSMutableParagraphStyle *)spacerStyleWithHeight:(CGFloat)height spacing:(CGFloat)spacing;
|
|
41
|
+
- (NSMutableParagraphStyle *)blockSpacerStyleWithMargin:(CGFloat)margin;
|
|
42
|
+
- (void)registerLinkRange:(NSRange)range url:(NSString *)url;
|
|
43
|
+
- (void)setBlockStyle:(BlockType)type
|
|
44
|
+
fontSize:(CGFloat)fontSize
|
|
45
|
+
fontFamily:(NSString *)fontFamily
|
|
46
|
+
fontWeight:(NSString *)fontWeight
|
|
47
|
+
color:(UIColor *)color;
|
|
48
|
+
- (void)setBlockStyle:(BlockType)type
|
|
49
|
+
fontSize:(CGFloat)fontSize
|
|
50
|
+
fontFamily:(NSString *)fontFamily
|
|
51
|
+
fontWeight:(NSString *)fontWeight
|
|
52
|
+
color:(UIColor *)color
|
|
53
|
+
headingLevel:(NSInteger)headingLevel;
|
|
54
|
+
- (void)setBlockStyle:(BlockType)type font:(UIFont *)font color:(UIColor *)color headingLevel:(NSInteger)headingLevel;
|
|
55
|
+
- (BlockStyle *)getBlockStyle;
|
|
56
|
+
- (NSDictionary *)getTextAttributes;
|
|
57
|
+
- (void)clearBlockStyle;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Checks if colors should be preserved based on existing attributes.
|
|
61
|
+
* Returns YES if the text is inside a link or inline code, which means
|
|
62
|
+
* we should preserve their colors instead of applying new colors.
|
|
63
|
+
*/
|
|
64
|
+
+ (BOOL)shouldPreserveColors:(NSDictionary *)existingAttributes;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Calculates the color that strong would use based on the configured strong color and block style.
|
|
68
|
+
* Uses strongColor if explicitly set (different from block color), otherwise uses block color.
|
|
69
|
+
*/
|
|
70
|
+
+ (UIColor *)calculateStrongColor:(UIColor *)configStrongColor blockColor:(UIColor *)blockColor;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Calculates the range for content rendered between start and current output length.
|
|
74
|
+
* Returns a range with length 0 if no content was rendered.
|
|
75
|
+
*/
|
|
76
|
+
+ (NSRange)rangeForRenderedContent:(NSMutableAttributedString *)output start:(NSUInteger)start;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Applies font and color attributes conditionally, only updating if they've changed.
|
|
80
|
+
* Returns YES if any attributes were updated, NO otherwise.
|
|
81
|
+
*/
|
|
82
|
+
+ (BOOL)applyFontAndColorAttributes:(NSMutableAttributedString *)output
|
|
83
|
+
range:(NSRange)range
|
|
84
|
+
font:(UIFont *)font
|
|
85
|
+
color:(UIColor *)color
|
|
86
|
+
existingAttributes:(NSDictionary *)existingAttributes
|
|
87
|
+
shouldPreserveColors:(BOOL)shouldPreserveColors;
|
|
88
|
+
@end
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#import "RenderContext.h"
|
|
2
|
+
#import "CodeBackground.h"
|
|
3
|
+
#import <React/RCTFont.h>
|
|
4
|
+
|
|
5
|
+
@implementation BlockStyle
|
|
6
|
+
@end
|
|
7
|
+
|
|
8
|
+
@implementation RenderContext {
|
|
9
|
+
NSMutableDictionary<NSString *, UIFont *> *_fontCache;
|
|
10
|
+
NSParagraphStyle *_baseSpacerTemplate;
|
|
11
|
+
NSParagraphStyle *_baseBlockSpacerTemplate;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
- (instancetype)init
|
|
15
|
+
{
|
|
16
|
+
if (self = [super init]) {
|
|
17
|
+
_linkRanges = [NSMutableArray array];
|
|
18
|
+
_linkURLs = [NSMutableArray array];
|
|
19
|
+
_fontCache = [NSMutableDictionary dictionary];
|
|
20
|
+
_currentBlockStyle = [[BlockStyle alloc] init];
|
|
21
|
+
|
|
22
|
+
NSMutableParagraphStyle *spacerTemplate = [[NSMutableParagraphStyle alloc] init];
|
|
23
|
+
_baseSpacerTemplate = [spacerTemplate copy];
|
|
24
|
+
|
|
25
|
+
NSMutableParagraphStyle *blockSpacerTemplate = [[NSMutableParagraphStyle alloc] init];
|
|
26
|
+
blockSpacerTemplate.minimumLineHeight = 1;
|
|
27
|
+
blockSpacerTemplate.maximumLineHeight = 1;
|
|
28
|
+
_baseBlockSpacerTemplate = [blockSpacerTemplate copy];
|
|
29
|
+
|
|
30
|
+
[self reset];
|
|
31
|
+
}
|
|
32
|
+
return self;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#pragma mark - Font Cache
|
|
36
|
+
|
|
37
|
+
- (UIFont *)cachedFontForSize:(CGFloat)fontSize family:(NSString *)fontFamily weight:(NSString *)fontWeight
|
|
38
|
+
{
|
|
39
|
+
NSString *weightKey = (fontWeight.length > 0) ? fontWeight : @"";
|
|
40
|
+
NSString *key = [NSString stringWithFormat:@"%.1f|%@|%@", fontSize, fontFamily ?: @"", weightKey];
|
|
41
|
+
|
|
42
|
+
UIFont *cached = _fontCache[key];
|
|
43
|
+
if (cached) {
|
|
44
|
+
return cached;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
NSString *effectiveWeight = (fontWeight.length > 0) ? fontWeight : nil;
|
|
48
|
+
|
|
49
|
+
UIFont *font = [RCTFont updateFont:nil
|
|
50
|
+
withFamily:fontFamily
|
|
51
|
+
size:@(fontSize)
|
|
52
|
+
weight:effectiveWeight
|
|
53
|
+
style:nil
|
|
54
|
+
variant:nil
|
|
55
|
+
scaleMultiplier:1];
|
|
56
|
+
|
|
57
|
+
if (font) {
|
|
58
|
+
_fontCache[key] = font;
|
|
59
|
+
}
|
|
60
|
+
return font;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#pragma mark - Paragraph Style Factory
|
|
64
|
+
|
|
65
|
+
- (NSMutableParagraphStyle *)spacerStyleWithHeight:(CGFloat)height spacing:(CGFloat)spacing
|
|
66
|
+
{
|
|
67
|
+
NSMutableParagraphStyle *style = [_baseSpacerTemplate mutableCopy];
|
|
68
|
+
style.minimumLineHeight = height;
|
|
69
|
+
style.maximumLineHeight = height;
|
|
70
|
+
style.paragraphSpacing = spacing;
|
|
71
|
+
return style;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
- (NSMutableParagraphStyle *)blockSpacerStyleWithMargin:(CGFloat)margin
|
|
75
|
+
{
|
|
76
|
+
NSMutableParagraphStyle *style = [_baseBlockSpacerTemplate mutableCopy];
|
|
77
|
+
style.paragraphSpacing = margin;
|
|
78
|
+
return style;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#pragma mark - Link Registry
|
|
82
|
+
|
|
83
|
+
- (void)registerLinkRange:(NSRange)range url:(NSString *)url
|
|
84
|
+
{
|
|
85
|
+
if (range.length == 0)
|
|
86
|
+
return;
|
|
87
|
+
[self.linkRanges addObject:[NSValue valueWithRange:range]];
|
|
88
|
+
[self.linkURLs addObject:url ?: @""];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#pragma mark - Block Style Management
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Updates the shared BlockStyle object with new traits.
|
|
95
|
+
* This avoids allocating a new object for every block node in the AST.
|
|
96
|
+
*/
|
|
97
|
+
- (void)setBlockStyle:(BlockType)type
|
|
98
|
+
fontSize:(CGFloat)fontSize
|
|
99
|
+
fontFamily:(NSString *)fontFamily
|
|
100
|
+
fontWeight:(NSString *)fontWeight
|
|
101
|
+
color:(UIColor *)color
|
|
102
|
+
headingLevel:(NSInteger)headingLevel
|
|
103
|
+
{
|
|
104
|
+
_currentBlockType = type;
|
|
105
|
+
_currentHeadingLevel = headingLevel;
|
|
106
|
+
|
|
107
|
+
_currentBlockStyle.fontSize = fontSize;
|
|
108
|
+
_currentBlockStyle.fontFamily = fontFamily ?: @"";
|
|
109
|
+
_currentBlockStyle.fontWeight = fontWeight ?: @"normal";
|
|
110
|
+
_currentBlockStyle.color = color ?: [UIColor blackColor];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
- (void)setBlockStyle:(BlockType)type
|
|
114
|
+
fontSize:(CGFloat)fontSize
|
|
115
|
+
fontFamily:(NSString *)fontFamily
|
|
116
|
+
fontWeight:(NSString *)fontWeight
|
|
117
|
+
color:(UIColor *)color
|
|
118
|
+
{
|
|
119
|
+
[self setBlockStyle:type fontSize:fontSize fontFamily:fontFamily fontWeight:fontWeight color:color headingLevel:0];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
- (void)setBlockStyle:(BlockType)type font:(UIFont *)font color:(UIColor *)color headingLevel:(NSInteger)headingLevel
|
|
123
|
+
{
|
|
124
|
+
_currentBlockType = type;
|
|
125
|
+
_currentHeadingLevel = headingLevel;
|
|
126
|
+
|
|
127
|
+
UIColor *finalColor = color ?: [UIColor blackColor];
|
|
128
|
+
_currentBlockStyle.cachedFont = font;
|
|
129
|
+
_currentBlockStyle.color = finalColor;
|
|
130
|
+
|
|
131
|
+
// Pre-create text attributes dictionary if we have both font and color
|
|
132
|
+
if (font) {
|
|
133
|
+
_currentBlockStyle.cachedTextAttributes =
|
|
134
|
+
@{NSFontAttributeName : font, NSForegroundColorAttributeName : finalColor};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
- (BlockStyle *)getBlockStyle
|
|
139
|
+
{
|
|
140
|
+
return _currentBlockStyle;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
- (NSDictionary *)getTextAttributes
|
|
144
|
+
{
|
|
145
|
+
BlockStyle *style = _currentBlockStyle;
|
|
146
|
+
|
|
147
|
+
// Return pre-cached attributes if available
|
|
148
|
+
if (style.cachedTextAttributes) {
|
|
149
|
+
return style.cachedTextAttributes;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Fall back to creating attributes from font cache
|
|
153
|
+
UIFont *font = style.cachedFont;
|
|
154
|
+
if (!font) {
|
|
155
|
+
font = [self cachedFontForSize:style.fontSize family:style.fontFamily weight:style.fontWeight];
|
|
156
|
+
}
|
|
157
|
+
UIColor *color = style.color ?: [UIColor blackColor];
|
|
158
|
+
|
|
159
|
+
// Cache for future calls within same block
|
|
160
|
+
style.cachedTextAttributes = @{NSFontAttributeName : font, NSForegroundColorAttributeName : color};
|
|
161
|
+
return style.cachedTextAttributes;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
- (void)clearBlockStyle
|
|
165
|
+
{
|
|
166
|
+
_currentBlockType = BlockTypeNone;
|
|
167
|
+
_currentHeadingLevel = 0;
|
|
168
|
+
_currentBlockStyle.cachedFont = nil;
|
|
169
|
+
_currentBlockStyle.cachedTextAttributes = nil;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
#pragma mark - Reset
|
|
173
|
+
|
|
174
|
+
- (void)reset
|
|
175
|
+
{
|
|
176
|
+
[_linkRanges removeAllObjects];
|
|
177
|
+
[_linkURLs removeAllObjects];
|
|
178
|
+
[self clearBlockStyle];
|
|
179
|
+
|
|
180
|
+
_blockquoteDepth = 0;
|
|
181
|
+
_listDepth = 0;
|
|
182
|
+
_listType = ListTypeUnordered;
|
|
183
|
+
_listItemNumber = 0;
|
|
184
|
+
|
|
185
|
+
// Revert shared style object to baseline defaults
|
|
186
|
+
_currentBlockStyle.fontSize = 0;
|
|
187
|
+
_currentBlockStyle.fontFamily = @"";
|
|
188
|
+
_currentBlockStyle.fontWeight = @"";
|
|
189
|
+
_currentBlockStyle.color = [UIColor blackColor];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
#pragma mark - Static Utilities
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Determines if specific inline attributes should protect the current color.
|
|
196
|
+
*/
|
|
197
|
+
+ (BOOL)shouldPreserveColors:(NSDictionary *)attrs
|
|
198
|
+
{
|
|
199
|
+
return (attrs[NSLinkAttributeName] != nil || attrs[CodeAttributeName] != nil);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Calculates whether a strong color should override the block color.
|
|
204
|
+
*/
|
|
205
|
+
+ (UIColor *)calculateStrongColor:(UIColor *)configStrongColor blockColor:(UIColor *)blockColor
|
|
206
|
+
{
|
|
207
|
+
if (!configStrongColor || [configStrongColor isEqual:blockColor]) {
|
|
208
|
+
return blockColor;
|
|
209
|
+
}
|
|
210
|
+
return configStrongColor;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Safely calculates a range based on a start point and the current output length.
|
|
215
|
+
*/
|
|
216
|
+
+ (NSRange)rangeForRenderedContent:(NSMutableAttributedString *)output start:(NSUInteger)start
|
|
217
|
+
{
|
|
218
|
+
if (output.length < start)
|
|
219
|
+
return NSMakeRange(start, 0);
|
|
220
|
+
return NSMakeRange(start, output.length - start);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Surgically applies attributes only if they differ from current values.
|
|
225
|
+
* This minimizes "dirtying" the AttributedString, which improves layout performance.
|
|
226
|
+
*/
|
|
227
|
+
+ (void)applyFontAndColorAttributes:(NSMutableAttributedString *)output
|
|
228
|
+
range:(NSRange)range
|
|
229
|
+
font:(UIFont *)font
|
|
230
|
+
color:(UIColor *)color
|
|
231
|
+
existingAttributes:(NSDictionary *)attrs
|
|
232
|
+
shouldPreserveColors:(BOOL)shouldPreserve
|
|
233
|
+
{
|
|
234
|
+
if (range.length == 0)
|
|
235
|
+
return;
|
|
236
|
+
|
|
237
|
+
// Font Update: Only if it exists and is different
|
|
238
|
+
if (font && ![font isEqual:attrs[NSFontAttributeName]]) {
|
|
239
|
+
[output addAttribute:NSFontAttributeName value:font range:range];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Color Update: Only if not a link/code and different from existing
|
|
243
|
+
if (color && !shouldPreserve && ![color isEqual:attrs[NSForegroundColorAttributeName]]) {
|
|
244
|
+
[output addAttribute:NSForegroundColorAttributeName value:color range:range];
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
@end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#import "MarkdownASTNode.h"
|
|
2
|
+
#import "NodeRenderer.h"
|
|
3
|
+
|
|
4
|
+
@class RenderContext;
|
|
5
|
+
|
|
6
|
+
@interface RendererFactory : NSObject
|
|
7
|
+
- (instancetype)initWithConfig:(id)config;
|
|
8
|
+
- (id<NodeRenderer>)rendererForNodeType:(MarkdownNodeType)type;
|
|
9
|
+
- (void)renderChildrenOfNode:(MarkdownASTNode *)node
|
|
10
|
+
into:(NSMutableAttributedString *)output
|
|
11
|
+
context:(RenderContext *)context;
|
|
12
|
+
@end
|