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,159 @@
|
|
|
1
|
+
#import "BlockquoteRenderer.h"
|
|
2
|
+
#import "BlockquoteBorder.h"
|
|
3
|
+
#import "FontUtils.h"
|
|
4
|
+
#import "MarkdownASTNode.h"
|
|
5
|
+
#import "ParagraphStyleUtils.h"
|
|
6
|
+
#import "RendererFactory.h"
|
|
7
|
+
#import "StyleConfig.h"
|
|
8
|
+
|
|
9
|
+
static NSString *const kNestedInfoDepthKey = @"depth";
|
|
10
|
+
static NSString *const kNestedInfoRangeKey = @"range";
|
|
11
|
+
|
|
12
|
+
@implementation BlockquoteRenderer {
|
|
13
|
+
RendererFactory *_rendererFactory;
|
|
14
|
+
StyleConfig *_config;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
- (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
|
|
18
|
+
{
|
|
19
|
+
if (self = [super init]) {
|
|
20
|
+
_rendererFactory = rendererFactory;
|
|
21
|
+
_config = (StyleConfig *)config;
|
|
22
|
+
}
|
|
23
|
+
return self;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
- (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
|
|
27
|
+
{
|
|
28
|
+
NSInteger currentDepth = context.blockquoteDepth;
|
|
29
|
+
context.blockquoteDepth = currentDepth + 1;
|
|
30
|
+
|
|
31
|
+
[context setBlockStyle:BlockTypeBlockquote
|
|
32
|
+
font:[_config blockquoteFont]
|
|
33
|
+
color:[_config blockquoteColor]
|
|
34
|
+
headingLevel:0];
|
|
35
|
+
|
|
36
|
+
NSUInteger start = output.length;
|
|
37
|
+
@try {
|
|
38
|
+
[_rendererFactory renderChildrenOfNode:node into:output context:context];
|
|
39
|
+
} @finally {
|
|
40
|
+
[context clearBlockStyle];
|
|
41
|
+
context.blockquoteDepth = currentDepth;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
NSUInteger end = output.length;
|
|
45
|
+
if (end <= start) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
[self applyStylingAndSpacing:output start:start end:end currentDepth:currentDepth];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#pragma mark - Styling and Spacing
|
|
53
|
+
|
|
54
|
+
- (void)applyStylingAndSpacing:(NSMutableAttributedString *)output
|
|
55
|
+
start:(NSUInteger)start
|
|
56
|
+
end:(NSUInteger)end
|
|
57
|
+
currentDepth:(NSInteger)currentDepth
|
|
58
|
+
{
|
|
59
|
+
NSRange blockquoteRange = NSMakeRange(start, end - start);
|
|
60
|
+
CGFloat levelSpacing = [_config blockquoteBorderWidth] + [_config blockquoteGapWidth];
|
|
61
|
+
NSArray<NSDictionary *> *nestedInfo = [self collectNestedBlockquotes:output range:blockquoteRange depth:currentDepth];
|
|
62
|
+
|
|
63
|
+
// Apply base styling (indentation, depth, background, line height)
|
|
64
|
+
[self applyBaseBlockquoteStyle:output
|
|
65
|
+
range:blockquoteRange
|
|
66
|
+
depth:currentDepth
|
|
67
|
+
levelSpacing:levelSpacing
|
|
68
|
+
backgroundColor:[_config blockquoteBackgroundColor]
|
|
69
|
+
lineHeight:[_config blockquoteLineHeight]];
|
|
70
|
+
|
|
71
|
+
// Re-apply nested blockquote styles to restore their correct indentation
|
|
72
|
+
// (applyBaseBlockquoteStyle overwrites nested indents with the parent's indent)
|
|
73
|
+
[self reapplyNestedStyles:output nestedInfo:nestedInfo levelSpacing:levelSpacing];
|
|
74
|
+
|
|
75
|
+
// Apply bottom margin for top-level blockquotes only
|
|
76
|
+
if (currentDepth == 0) {
|
|
77
|
+
CGFloat marginBottom = [_config blockquoteMarginBottom];
|
|
78
|
+
if (marginBottom > 0) {
|
|
79
|
+
applyBlockSpacing(output, marginBottom);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#pragma mark - Nested Blockquote Handling
|
|
85
|
+
|
|
86
|
+
- (NSArray<NSDictionary *> *)collectNestedBlockquotes:(NSMutableAttributedString *)output
|
|
87
|
+
range:(NSRange)blockquoteRange
|
|
88
|
+
depth:(NSInteger)currentDepth
|
|
89
|
+
{
|
|
90
|
+
NSMutableArray<NSDictionary *> *nestedInfo = [NSMutableArray array];
|
|
91
|
+
|
|
92
|
+
[output
|
|
93
|
+
enumerateAttribute:BlockquoteDepthAttributeName
|
|
94
|
+
inRange:blockquoteRange
|
|
95
|
+
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
|
|
96
|
+
usingBlock:^(id value, NSRange range, BOOL *stop) {
|
|
97
|
+
NSInteger depth = [value integerValue];
|
|
98
|
+
if (value && depth > currentDepth) {
|
|
99
|
+
[nestedInfo
|
|
100
|
+
addObject:@{kNestedInfoDepthKey : value, kNestedInfoRangeKey : [NSValue valueWithRange:range]}];
|
|
101
|
+
}
|
|
102
|
+
}];
|
|
103
|
+
|
|
104
|
+
return nestedInfo;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
- (void)applyBaseBlockquoteStyle:(NSMutableAttributedString *)output
|
|
108
|
+
range:(NSRange)blockquoteRange
|
|
109
|
+
depth:(NSInteger)currentDepth
|
|
110
|
+
levelSpacing:(CGFloat)levelSpacing
|
|
111
|
+
backgroundColor:(UIColor *)backgroundColor
|
|
112
|
+
lineHeight:(CGFloat)lineHeight
|
|
113
|
+
{
|
|
114
|
+
NSMutableParagraphStyle *paragraphStyle = getOrCreateParagraphStyle(output, blockquoteRange.location);
|
|
115
|
+
CGFloat totalIndent = [self calculateIndentForDepth:currentDepth levelSpacing:levelSpacing];
|
|
116
|
+
paragraphStyle.firstLineHeadIndent = totalIndent;
|
|
117
|
+
paragraphStyle.headIndent = totalIndent;
|
|
118
|
+
|
|
119
|
+
NSMutableDictionary *newAttributes =
|
|
120
|
+
[NSMutableDictionary dictionaryWithObjectsAndKeys:paragraphStyle, NSParagraphStyleAttributeName, @(currentDepth),
|
|
121
|
+
BlockquoteDepthAttributeName, nil];
|
|
122
|
+
if (backgroundColor) {
|
|
123
|
+
newAttributes[BlockquoteBackgroundColorAttributeName] = backgroundColor;
|
|
124
|
+
}
|
|
125
|
+
[output addAttributes:newAttributes range:blockquoteRange];
|
|
126
|
+
|
|
127
|
+
applyLineHeight(output, blockquoteRange, lineHeight);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
- (void)reapplyNestedStyles:(NSMutableAttributedString *)output
|
|
131
|
+
nestedInfo:(NSArray<NSDictionary *> *)nestedInfo
|
|
132
|
+
levelSpacing:(CGFloat)levelSpacing
|
|
133
|
+
{
|
|
134
|
+
// Re-apply indentation to nested blockquotes since applyBaseBlockquoteStyle
|
|
135
|
+
// overwrote them with the parent's indentation
|
|
136
|
+
for (NSDictionary *info in nestedInfo) {
|
|
137
|
+
NSRange nestedRange = [info[kNestedInfoRangeKey] rangeValue];
|
|
138
|
+
NSInteger nestedDepth = [info[kNestedInfoDepthKey] integerValue];
|
|
139
|
+
NSMutableParagraphStyle *style = getOrCreateParagraphStyle(output, nestedRange.location);
|
|
140
|
+
|
|
141
|
+
CGFloat indent = [self calculateIndentForDepth:nestedDepth levelSpacing:levelSpacing];
|
|
142
|
+
style.firstLineHeadIndent = indent;
|
|
143
|
+
style.headIndent = indent;
|
|
144
|
+
style.tailIndent = 0;
|
|
145
|
+
|
|
146
|
+
[output
|
|
147
|
+
addAttributes:@{NSParagraphStyleAttributeName : style, BlockquoteDepthAttributeName : info[kNestedInfoDepthKey]}
|
|
148
|
+
range:nestedRange];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#pragma mark - Helper Methods
|
|
153
|
+
|
|
154
|
+
- (CGFloat)calculateIndentForDepth:(NSInteger)depth levelSpacing:(CGFloat)levelSpacing
|
|
155
|
+
{
|
|
156
|
+
return (depth + 1) * levelSpacing;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#import "CodeBlockRenderer.h"
|
|
2
|
+
#import "CodeBlockBackground.h"
|
|
3
|
+
#import "LastElementUtils.h"
|
|
4
|
+
#import "MarkdownASTNode.h"
|
|
5
|
+
#import "ParagraphStyleUtils.h"
|
|
6
|
+
#import "RenderContext.h"
|
|
7
|
+
#import "RendererFactory.h"
|
|
8
|
+
#import "StyleConfig.h"
|
|
9
|
+
|
|
10
|
+
@implementation CodeBlockRenderer {
|
|
11
|
+
RendererFactory *_rendererFactory;
|
|
12
|
+
StyleConfig *_config;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
- (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
|
|
16
|
+
{
|
|
17
|
+
if (self = [super init]) {
|
|
18
|
+
_rendererFactory = rendererFactory;
|
|
19
|
+
_config = (StyleConfig *)config;
|
|
20
|
+
}
|
|
21
|
+
return self;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
- (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
|
|
25
|
+
{
|
|
26
|
+
[context setBlockStyle:BlockTypeCodeBlock font:[_config codeBlockFont] color:[_config codeBlockColor] headingLevel:0];
|
|
27
|
+
|
|
28
|
+
CGFloat padding = [_config codeBlockPadding];
|
|
29
|
+
CGFloat lineHeight = [_config codeBlockLineHeight];
|
|
30
|
+
CGFloat marginBottom = [_config codeBlockMarginBottom];
|
|
31
|
+
NSUInteger blockStart = output.length;
|
|
32
|
+
|
|
33
|
+
// 1. TOP PADDING: Inside the background
|
|
34
|
+
[output appendAttributedString:kNewlineAttributedString];
|
|
35
|
+
NSMutableParagraphStyle *topSpacerStyle = [context spacerStyleWithHeight:padding spacing:0];
|
|
36
|
+
[output addAttribute:NSParagraphStyleAttributeName value:topSpacerStyle range:NSMakeRange(blockStart, 1)];
|
|
37
|
+
|
|
38
|
+
// 2. RENDER CONTENT
|
|
39
|
+
NSUInteger contentStart = output.length;
|
|
40
|
+
@try {
|
|
41
|
+
[_rendererFactory renderChildrenOfNode:node into:output context:context];
|
|
42
|
+
} @finally {
|
|
43
|
+
[context clearBlockStyle];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
NSUInteger contentEnd = output.length;
|
|
47
|
+
if (contentEnd <= contentStart)
|
|
48
|
+
return;
|
|
49
|
+
|
|
50
|
+
NSRange contentRange = NSMakeRange(contentStart, contentEnd - contentStart);
|
|
51
|
+
|
|
52
|
+
// 3. CONTENT STYLING
|
|
53
|
+
UIFont *codeFont = [_config codeBlockFont];
|
|
54
|
+
UIColor *codeColor = [_config codeBlockColor];
|
|
55
|
+
if (codeColor) {
|
|
56
|
+
[output addAttributes:@{NSFontAttributeName : codeFont, NSForegroundColorAttributeName : codeColor}
|
|
57
|
+
range:contentRange];
|
|
58
|
+
} else {
|
|
59
|
+
[output addAttribute:NSFontAttributeName value:codeFont range:contentRange];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (lineHeight > 0) {
|
|
63
|
+
applyLineHeight(output, contentRange, lineHeight);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// HORIZONTAL INDENTS
|
|
67
|
+
NSMutableParagraphStyle *baseStyle = [getOrCreateParagraphStyle(output, contentStart) mutableCopy];
|
|
68
|
+
baseStyle.firstLineHeadIndent = padding;
|
|
69
|
+
baseStyle.headIndent = padding;
|
|
70
|
+
baseStyle.tailIndent = -padding;
|
|
71
|
+
[output addAttribute:NSParagraphStyleAttributeName value:baseStyle range:contentRange];
|
|
72
|
+
|
|
73
|
+
// 4. BOTTOM PADDING: Inside the background
|
|
74
|
+
NSUInteger bottomPaddingStart = output.length;
|
|
75
|
+
[output appendAttributedString:kNewlineAttributedString];
|
|
76
|
+
NSMutableParagraphStyle *bottomPaddingStyle = [context spacerStyleWithHeight:padding spacing:0];
|
|
77
|
+
[output addAttribute:NSParagraphStyleAttributeName value:bottomPaddingStyle range:NSMakeRange(bottomPaddingStart, 1)];
|
|
78
|
+
|
|
79
|
+
// MARK BACKGROUND: Ends here to exclude the margin
|
|
80
|
+
NSRange backgroundRange = NSMakeRange(blockStart, output.length - blockStart);
|
|
81
|
+
[output addAttribute:CodeBlockAttributeName value:@YES range:backgroundRange];
|
|
82
|
+
|
|
83
|
+
// 5. EXTERNAL MARGIN: Outside the background
|
|
84
|
+
if (marginBottom > 0) {
|
|
85
|
+
applyBlockSpacing(output, marginBottom);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#import "CodeRenderer.h"
|
|
2
|
+
#import "CodeBackground.h"
|
|
3
|
+
#import "FontUtils.h"
|
|
4
|
+
#import "MarkdownASTNode.h"
|
|
5
|
+
#import "RenderContext.h"
|
|
6
|
+
#import "RendererFactory.h"
|
|
7
|
+
#import "StyleConfig.h"
|
|
8
|
+
|
|
9
|
+
@implementation CodeRenderer {
|
|
10
|
+
RendererFactory *_rendererFactory;
|
|
11
|
+
StyleConfig *_config;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
- (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
|
|
15
|
+
{
|
|
16
|
+
self = [super init];
|
|
17
|
+
if (self) {
|
|
18
|
+
_rendererFactory = rendererFactory;
|
|
19
|
+
_config = (StyleConfig *)config;
|
|
20
|
+
}
|
|
21
|
+
return self;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
- (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
|
|
25
|
+
{
|
|
26
|
+
|
|
27
|
+
BlockStyle *blockStyle = [context getBlockStyle];
|
|
28
|
+
|
|
29
|
+
UIColor *codeColor = _config.codeColor;
|
|
30
|
+
|
|
31
|
+
UIFont *blockFont = cachedFontFromBlockStyle(blockStyle, context);
|
|
32
|
+
|
|
33
|
+
UIFontDescriptorSymbolicTraits traits = blockFont.fontDescriptor.symbolicTraits;
|
|
34
|
+
UIFontWeight weight = (traits & UIFontDescriptorTraitBold) ? UIFontWeightBold : UIFontWeightRegular;
|
|
35
|
+
|
|
36
|
+
UIFont *monospacedFont = [UIFont monospacedSystemFontOfSize:blockStyle.fontSize weight:weight];
|
|
37
|
+
|
|
38
|
+
NSUInteger start = output.length;
|
|
39
|
+
|
|
40
|
+
[_rendererFactory renderChildrenOfNode:node into:output context:context];
|
|
41
|
+
|
|
42
|
+
NSRange range = [RenderContext rangeForRenderedContent:output start:start];
|
|
43
|
+
if (range.length > 0) {
|
|
44
|
+
NSDictionary *existingAttributes = [output attributesAtIndex:start effectiveRange:NULL];
|
|
45
|
+
NSMutableDictionary *codeAttributes = [existingAttributes ?: @{} mutableCopy];
|
|
46
|
+
|
|
47
|
+
codeAttributes[NSFontAttributeName] = monospacedFont;
|
|
48
|
+
if (codeColor) {
|
|
49
|
+
codeAttributes[NSForegroundColorAttributeName] = codeColor;
|
|
50
|
+
}
|
|
51
|
+
codeAttributes[CodeAttributeName] = @YES;
|
|
52
|
+
|
|
53
|
+
// Store block line height directly for CodeBackground to use
|
|
54
|
+
codeAttributes[@"BlockLineHeight"] = @(blockFont.lineHeight);
|
|
55
|
+
|
|
56
|
+
[output setAttributes:codeAttributes range:range];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#import "EmphasisRenderer.h"
|
|
2
|
+
#import "FontUtils.h"
|
|
3
|
+
#import "MarkdownASTNode.h"
|
|
4
|
+
#import "RenderContext.h"
|
|
5
|
+
#import "RendererFactory.h"
|
|
6
|
+
#import "StyleConfig.h"
|
|
7
|
+
|
|
8
|
+
@implementation EmphasisRenderer {
|
|
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
|
+
NSUInteger start = output.length;
|
|
28
|
+
[_rendererFactory renderChildrenOfNode:node into:output context:context];
|
|
29
|
+
|
|
30
|
+
NSRange range = NSMakeRange(start, output.length - start);
|
|
31
|
+
if (range.length == 0)
|
|
32
|
+
return;
|
|
33
|
+
|
|
34
|
+
BlockStyle *blockStyle = [context getBlockStyle];
|
|
35
|
+
UIColor *configEmphasisColor = [_config emphasisColor];
|
|
36
|
+
|
|
37
|
+
// Cache the Strong color calculation to efficiently detect nested Strong nodes
|
|
38
|
+
UIColor *strongColorToPreserve = [_config strongColor] ? [RenderContext calculateStrongColor:[_config strongColor]
|
|
39
|
+
blockColor:blockStyle.color]
|
|
40
|
+
: nil;
|
|
41
|
+
|
|
42
|
+
[output
|
|
43
|
+
enumerateAttributesInRange:range
|
|
44
|
+
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
|
|
45
|
+
usingBlock:^(NSDictionary<NSAttributedStringKey, id> *attrs, NSRange subrange, BOOL *stop) {
|
|
46
|
+
// 1. Font Optimization: Only apply italic trait if not already present
|
|
47
|
+
UIFont *currentFont =
|
|
48
|
+
attrs[NSFontAttributeName] ?: cachedFontFromBlockStyle(blockStyle, context);
|
|
49
|
+
if (currentFont && !(currentFont.fontDescriptor.symbolicTraits & UIFontDescriptorTraitItalic)) {
|
|
50
|
+
UIFont *italicFont = [self ensureFontIsItalic:currentFont];
|
|
51
|
+
if (italicFont) {
|
|
52
|
+
[output addAttribute:NSFontAttributeName value:italicFont range:subrange];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 2. Color Optimization: Handle nesting and avoid redundant updates
|
|
57
|
+
if (configEmphasisColor) {
|
|
58
|
+
UIColor *currentColor = attrs[NSForegroundColorAttributeName];
|
|
59
|
+
BOOL isLink = attrs[NSLinkAttributeName] != nil;
|
|
60
|
+
|
|
61
|
+
// Verify if the current color belongs to a Strong parent
|
|
62
|
+
BOOL isStrongColor = strongColorToPreserve && [currentColor isEqual:strongColorToPreserve];
|
|
63
|
+
|
|
64
|
+
// Preserving colors for higher-priority elements (links, strong nodes, etc.)
|
|
65
|
+
if (!isLink && !isStrongColor && ![RenderContext shouldPreserveColors:attrs]) {
|
|
66
|
+
// Only modify the string if the color is actually different
|
|
67
|
+
if (![currentColor isEqual:configEmphasisColor]) {
|
|
68
|
+
[output addAttribute:NSForegroundColorAttributeName
|
|
69
|
+
value:configEmphasisColor
|
|
70
|
+
range:subrange];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#pragma mark - Helper Methods
|
|
78
|
+
|
|
79
|
+
- (UIFont *)ensureFontIsItalic:(UIFont *)font
|
|
80
|
+
{
|
|
81
|
+
if (!font)
|
|
82
|
+
return nil;
|
|
83
|
+
|
|
84
|
+
UIFontDescriptorSymbolicTraits traits = font.fontDescriptor.symbolicTraits;
|
|
85
|
+
if (traits & UIFontDescriptorTraitItalic)
|
|
86
|
+
return font;
|
|
87
|
+
|
|
88
|
+
// Combine italic with existing traits (e.g., preserving Bold if present)
|
|
89
|
+
UIFontDescriptorSymbolicTraits combinedTraits = traits | UIFontDescriptorTraitItalic;
|
|
90
|
+
UIFontDescriptor *italicDescriptor = [font.fontDescriptor fontDescriptorWithSymbolicTraits:combinedTraits];
|
|
91
|
+
|
|
92
|
+
// Size 0 in fontWithDescriptor:size: maintains the current point size
|
|
93
|
+
return [UIFont fontWithDescriptor:italicDescriptor size:0] ?: font;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#import "HeadingRenderer.h"
|
|
2
|
+
#import "FontUtils.h"
|
|
3
|
+
#import "ParagraphStyleUtils.h"
|
|
4
|
+
#import "RenderContext.h"
|
|
5
|
+
#import "RendererFactory.h"
|
|
6
|
+
#import "RuntimeKeys.h"
|
|
7
|
+
#import "StyleConfig.h"
|
|
8
|
+
|
|
9
|
+
// Lightweight struct to hold style data without object overhead
|
|
10
|
+
typedef struct {
|
|
11
|
+
__unsafe_unretained UIFont *font;
|
|
12
|
+
__unsafe_unretained UIColor *color;
|
|
13
|
+
CGFloat marginBottom;
|
|
14
|
+
CGFloat lineHeight;
|
|
15
|
+
} HeadingStyle;
|
|
16
|
+
|
|
17
|
+
// Static heading type strings (index 0 unused, 1-6 for h1-h6)
|
|
18
|
+
static NSString *const kHeadingTypes[] = {nil, @"heading-1", @"heading-2", @"heading-3",
|
|
19
|
+
@"heading-4", @"heading-5", @"heading-6"};
|
|
20
|
+
|
|
21
|
+
@implementation HeadingRenderer {
|
|
22
|
+
RendererFactory *_rendererFactory;
|
|
23
|
+
StyleConfig *_config;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
- (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
|
|
27
|
+
{
|
|
28
|
+
if (self = [super init]) {
|
|
29
|
+
_rendererFactory = rendererFactory;
|
|
30
|
+
_config = (StyleConfig *)config;
|
|
31
|
+
}
|
|
32
|
+
return self;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#pragma mark - Rendering
|
|
36
|
+
|
|
37
|
+
- (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
|
|
38
|
+
{
|
|
39
|
+
NSInteger level = [node.attributes[@"level"] integerValue];
|
|
40
|
+
if (level < 1 || level > 6)
|
|
41
|
+
level = 1;
|
|
42
|
+
|
|
43
|
+
// Fetch style struct with pre-cached font from StyleConfig
|
|
44
|
+
HeadingStyle style = [self styleForLevel:level];
|
|
45
|
+
|
|
46
|
+
[context setBlockStyle:BlockTypeHeading font:style.font color:style.color headingLevel:level];
|
|
47
|
+
|
|
48
|
+
NSUInteger start = output.length;
|
|
49
|
+
@try {
|
|
50
|
+
[_rendererFactory renderChildrenOfNode:node into:output context:context];
|
|
51
|
+
} @finally {
|
|
52
|
+
[context clearBlockStyle];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
NSRange range = NSMakeRange(start, output.length - start);
|
|
56
|
+
if (range.length == 0)
|
|
57
|
+
return;
|
|
58
|
+
|
|
59
|
+
// Mark as heading for HTML generation and Copy as Markdown
|
|
60
|
+
[output addAttribute:MarkdownTypeAttributeName value:kHeadingTypes[level] range:range];
|
|
61
|
+
|
|
62
|
+
applyLineHeight(output, range, style.lineHeight);
|
|
63
|
+
applyParagraphSpacing(output, start, style.marginBottom);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#pragma mark - Optimized Style Provider
|
|
67
|
+
|
|
68
|
+
- (HeadingStyle)styleForLevel:(NSInteger)level
|
|
69
|
+
{
|
|
70
|
+
StyleConfig *c = _config;
|
|
71
|
+
HeadingStyle s;
|
|
72
|
+
|
|
73
|
+
switch (level) {
|
|
74
|
+
case 1:
|
|
75
|
+
s = (HeadingStyle){c.h1Font, c.h1Color, c.h1MarginBottom, c.h1LineHeight};
|
|
76
|
+
break;
|
|
77
|
+
case 2:
|
|
78
|
+
s = (HeadingStyle){c.h2Font, c.h2Color, c.h2MarginBottom, c.h2LineHeight};
|
|
79
|
+
break;
|
|
80
|
+
case 3:
|
|
81
|
+
s = (HeadingStyle){c.h3Font, c.h3Color, c.h3MarginBottom, c.h3LineHeight};
|
|
82
|
+
break;
|
|
83
|
+
case 4:
|
|
84
|
+
s = (HeadingStyle){c.h4Font, c.h4Color, c.h4MarginBottom, c.h4LineHeight};
|
|
85
|
+
break;
|
|
86
|
+
case 5:
|
|
87
|
+
s = (HeadingStyle){c.h5Font, c.h5Color, c.h5MarginBottom, c.h5LineHeight};
|
|
88
|
+
break;
|
|
89
|
+
case 6:
|
|
90
|
+
s = (HeadingStyle){c.h6Font, c.h6Color, c.h6MarginBottom, c.h6LineHeight};
|
|
91
|
+
break;
|
|
92
|
+
default:
|
|
93
|
+
return [self styleForLevel:1];
|
|
94
|
+
}
|
|
95
|
+
return s;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#import "ImageRenderer.h"
|
|
2
|
+
#import "ImageAttachment.h"
|
|
3
|
+
#import "MarkdownASTNode.h"
|
|
4
|
+
#import "RenderContext.h"
|
|
5
|
+
#import "RendererFactory.h"
|
|
6
|
+
#import "StyleConfig.h"
|
|
7
|
+
|
|
8
|
+
static const unichar kLineBreak = '\n';
|
|
9
|
+
static const unichar kZeroWidthSpace = 0x200B;
|
|
10
|
+
|
|
11
|
+
@implementation ImageRenderer {
|
|
12
|
+
RendererFactory *_rendererFactory;
|
|
13
|
+
StyleConfig *_config;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
- (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
|
|
17
|
+
{
|
|
18
|
+
if (self = [super init]) {
|
|
19
|
+
_rendererFactory = rendererFactory;
|
|
20
|
+
_config = (StyleConfig *)config;
|
|
21
|
+
}
|
|
22
|
+
return self;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#pragma mark - Rendering
|
|
26
|
+
|
|
27
|
+
- (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
|
|
28
|
+
{
|
|
29
|
+
NSString *imageURL = node.attributes[@"url"];
|
|
30
|
+
// Safety check for URL presence and length
|
|
31
|
+
if (!imageURL || imageURL.length == 0) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Determine if this image is being placed inside an existing line of text
|
|
36
|
+
BOOL isInline = [self isInlineImageInOutput:output];
|
|
37
|
+
|
|
38
|
+
// Create the attachment using the shared config
|
|
39
|
+
ImageAttachment *attachment = [[ImageAttachment alloc] initWithImageURL:imageURL config:_config isInline:isInline];
|
|
40
|
+
|
|
41
|
+
// Append the attachment character to the output
|
|
42
|
+
NSAttributedString *imageString = [NSAttributedString attributedStringWithAttachment:attachment];
|
|
43
|
+
[output appendAttributedString:imageString];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#pragma mark - Private Helpers
|
|
47
|
+
|
|
48
|
+
- (BOOL)isInlineImageInOutput:(NSAttributedString *)output
|
|
49
|
+
{
|
|
50
|
+
if (output.length == 0) {
|
|
51
|
+
return NO;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check the last character to see if we are currently mid-paragraph
|
|
55
|
+
unichar lastChar = [output.string characterAtIndex:output.length - 1];
|
|
56
|
+
|
|
57
|
+
// If the last character is a newline or a zero-width space (often used as block separators),
|
|
58
|
+
// we consider the next image to be a "block" image.
|
|
59
|
+
return (lastChar != kLineBreak && lastChar != kZeroWidthSpace);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@end
|