react-native-enriched-markdown 0.1.0 → 0.2.0
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 +551 -0
- package/ReactNativeEnrichedMarkdown.podspec +27 -0
- package/android/build.gradle +101 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +54 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +26 -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 +33 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +31 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +82 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +1388 -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 +220 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +270 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextLayoutManager.kt +15 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +173 -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 +385 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/accessibility/MarkdownAccessibilityHelper.kt +279 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkLongPressEvent.kt +23 -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 +31 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/parser/Parser.kt +62 -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 +84 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeBlockRenderer.kt +104 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeRenderer.kt +36 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/DocumentRenderer.kt +16 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/EmphasisRenderer.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/HeadingRenderer.kt +70 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ImageRenderer.kt +68 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LineBreakRenderer.kt +16 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +29 -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 +59 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListRenderer.kt +76 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/NodeRenderer.kt +103 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ParagraphRenderer.kt +80 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/Renderer.kt +109 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +86 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrikethroughRenderer.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrongRenderer.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/TextRenderer.kt +30 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ThematicBreakRenderer.kt +45 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/UnderlineRenderer.kt +27 -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 +321 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LineHeightSpan.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +51 -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/StrikethroughSpan.kt +12 -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 +11 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/BlockquoteStyle.kt +51 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeBlockStyle.kt +54 -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 +33 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ImageStyle.kt +23 -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 +57 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ParagraphStyle.kt +33 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StrikethroughStyle.kt +17 -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 +211 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleParser.kt +92 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/TextAlignment.kt +32 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ThematicBreakStyle.kt +23 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/UnderlineStyle.kt +17 -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 +827 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/LinkLongPressMovementMethod.kt +121 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/MarkdownExtractor.kt +375 -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 +183 -0
- package/android/src/main/jni/CMakeLists.txt +70 -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 +20 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.h +37 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/conversions.h +22 -0
- package/cpp/md4c/md4c.c +6492 -0
- package/cpp/md4c/md4c.h +402 -0
- package/cpp/parser/MD4CParser.cpp +327 -0
- package/cpp/parser/MD4CParser.hpp +27 -0
- package/cpp/parser/MarkdownASTNode.hpp +51 -0
- package/ios/EnrichedMarkdownText.h +18 -0
- package/ios/EnrichedMarkdownText.mm +1401 -0
- package/ios/attachments/EnrichedMarkdownImageAttachment.h +23 -0
- package/ios/attachments/EnrichedMarkdownImageAttachment.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 +33 -0
- package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.h +31 -0
- package/ios/generated/EnrichedMarkdownTextSpec/Props.cpp +82 -0
- package/ios/generated/EnrichedMarkdownTextSpec/Props.h +1388 -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 +35 -0
- package/ios/parser/MarkdownASTNode.m +32 -0
- package/ios/parser/MarkdownParser.h +17 -0
- package/ios/parser/MarkdownParser.mm +42 -0
- package/ios/parser/MarkdownParserBridge.mm +120 -0
- package/ios/renderer/AttributedRenderer.h +11 -0
- package/ios/renderer/AttributedRenderer.m +152 -0
- package/ios/renderer/BlockquoteRenderer.h +7 -0
- package/ios/renderer/BlockquoteRenderer.m +160 -0
- package/ios/renderer/CodeBlockRenderer.h +10 -0
- package/ios/renderer/CodeBlockRenderer.m +90 -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 +105 -0
- package/ios/renderer/ImageRenderer.h +12 -0
- package/ios/renderer/ImageRenderer.m +83 -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 +103 -0
- package/ios/renderer/ListRenderer.h +13 -0
- package/ios/renderer/ListRenderer.m +70 -0
- package/ios/renderer/NodeRenderer.h +8 -0
- package/ios/renderer/ParagraphRenderer.h +7 -0
- package/ios/renderer/ParagraphRenderer.m +80 -0
- package/ios/renderer/RenderContext.h +105 -0
- package/ios/renderer/RenderContext.m +312 -0
- package/ios/renderer/RendererFactory.h +12 -0
- package/ios/renderer/RendererFactory.m +116 -0
- package/ios/renderer/StrikethroughRenderer.h +6 -0
- package/ios/renderer/StrikethroughRenderer.m +40 -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/renderer/UnderlineRenderer.h +6 -0
- package/ios/renderer/UnderlineRenderer.m +39 -0
- package/ios/styles/StyleConfig.h +274 -0
- package/ios/styles/StyleConfig.mm +1806 -0
- package/ios/utils/AccessibilityInfo.h +35 -0
- package/ios/utils/AccessibilityInfo.m +24 -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 +82 -0
- package/ios/utils/EditMenuUtils.h +22 -0
- package/ios/utils/EditMenuUtils.m +118 -0
- package/ios/utils/FontUtils.h +25 -0
- package/ios/utils/FontUtils.m +27 -0
- package/ios/utils/HTMLGenerator.h +20 -0
- package/ios/utils/HTMLGenerator.m +793 -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/MarkdownAccessibilityElementBuilder.h +45 -0
- package/ios/utils/MarkdownAccessibilityElementBuilder.m +323 -0
- package/ios/utils/MarkdownExtractor.h +17 -0
- package/ios/utils/MarkdownExtractor.m +308 -0
- package/ios/utils/ParagraphStyleUtils.h +21 -0
- package/ios/utils/ParagraphStyleUtils.m +111 -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 +65 -0
- package/lib/module/EnrichedMarkdownText.js.map +1 -0
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +210 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/normalizeMarkdownStyle.js +384 -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 +183 -0
- package/lib/typescript/src/EnrichedMarkdownText.d.ts.map +1 -0
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +185 -0
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +4 -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 +280 -0
- package/src/EnrichedMarkdownTextNativeComponent.ts +210 -0
- package/src/index.tsx +10 -0
- package/src/normalizeMarkdownStyle.ts +423 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
#import "RenderContext.h"
|
|
2
|
+
#import "CodeBackground.h"
|
|
3
|
+
#import "FontUtils.h"
|
|
4
|
+
#import <React/RCTFont.h>
|
|
5
|
+
|
|
6
|
+
@implementation BlockStyle
|
|
7
|
+
@end
|
|
8
|
+
|
|
9
|
+
@implementation RenderContext {
|
|
10
|
+
NSMutableDictionary<NSString *, UIFont *> *_fontCache;
|
|
11
|
+
NSParagraphStyle *_baseSpacerTemplate;
|
|
12
|
+
NSParagraphStyle *_baseBlockSpacerTemplate;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
- (instancetype)init
|
|
16
|
+
{
|
|
17
|
+
if (self = [super init]) {
|
|
18
|
+
_linkRanges = [NSMutableArray array];
|
|
19
|
+
_linkURLs = [NSMutableArray array];
|
|
20
|
+
_headingRanges = [NSMutableArray array];
|
|
21
|
+
_headingLevels = [NSMutableArray array];
|
|
22
|
+
_imageRanges = [NSMutableArray array];
|
|
23
|
+
_imageAltTexts = [NSMutableArray array];
|
|
24
|
+
_imageURLs = [NSMutableArray array];
|
|
25
|
+
_listItemRanges = [NSMutableArray array];
|
|
26
|
+
_listItemPositions = [NSMutableArray array];
|
|
27
|
+
_listItemDepths = [NSMutableArray array];
|
|
28
|
+
_listItemOrdered = [NSMutableArray array];
|
|
29
|
+
_fontCache = [NSMutableDictionary dictionary];
|
|
30
|
+
_currentBlockStyle = [[BlockStyle alloc] init];
|
|
31
|
+
_allowFontScaling = YES;
|
|
32
|
+
_maxFontSizeMultiplier = 0;
|
|
33
|
+
|
|
34
|
+
NSMutableParagraphStyle *spacerTemplate = [[NSMutableParagraphStyle alloc] init];
|
|
35
|
+
_baseSpacerTemplate = [spacerTemplate copy];
|
|
36
|
+
|
|
37
|
+
NSMutableParagraphStyle *blockSpacerTemplate = [[NSMutableParagraphStyle alloc] init];
|
|
38
|
+
blockSpacerTemplate.minimumLineHeight = 1;
|
|
39
|
+
blockSpacerTemplate.maximumLineHeight = 1;
|
|
40
|
+
_baseBlockSpacerTemplate = [blockSpacerTemplate copy];
|
|
41
|
+
|
|
42
|
+
[self reset];
|
|
43
|
+
}
|
|
44
|
+
return self;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
#pragma mark - Font Cache
|
|
48
|
+
|
|
49
|
+
- (UIFont *)cachedFontForSize:(CGFloat)fontSize family:(NSString *)fontFamily weight:(NSString *)fontWeight
|
|
50
|
+
{
|
|
51
|
+
CGFloat effectiveMultiplier = _allowFontScaling ? RCTFontSizeMultiplierWithMax(_maxFontSizeMultiplier) : 1.0;
|
|
52
|
+
|
|
53
|
+
NSString *weightKey = (fontWeight.length > 0) ? fontWeight : @"";
|
|
54
|
+
NSString *key =
|
|
55
|
+
[NSString stringWithFormat:@"%.1f|%@|%@|%.2f", fontSize, fontFamily ?: @"", weightKey, effectiveMultiplier];
|
|
56
|
+
|
|
57
|
+
UIFont *cached = _fontCache[key];
|
|
58
|
+
if (cached) {
|
|
59
|
+
return cached;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
NSString *effectiveWeight = (fontWeight.length > 0) ? fontWeight : nil;
|
|
63
|
+
|
|
64
|
+
UIFont *font = [RCTFont updateFont:nil
|
|
65
|
+
withFamily:fontFamily
|
|
66
|
+
size:@(fontSize)
|
|
67
|
+
weight:effectiveWeight
|
|
68
|
+
style:nil
|
|
69
|
+
variant:nil
|
|
70
|
+
scaleMultiplier:effectiveMultiplier];
|
|
71
|
+
|
|
72
|
+
if (font) {
|
|
73
|
+
_fontCache[key] = font;
|
|
74
|
+
}
|
|
75
|
+
return font;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#pragma mark - Paragraph Style Factory
|
|
79
|
+
|
|
80
|
+
- (NSMutableParagraphStyle *)spacerStyleWithHeight:(CGFloat)height spacing:(CGFloat)spacing
|
|
81
|
+
{
|
|
82
|
+
NSMutableParagraphStyle *style = [_baseSpacerTemplate mutableCopy];
|
|
83
|
+
style.minimumLineHeight = height;
|
|
84
|
+
style.maximumLineHeight = height;
|
|
85
|
+
style.paragraphSpacing = spacing;
|
|
86
|
+
return style;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
- (NSMutableParagraphStyle *)blockSpacerStyleWithMargin:(CGFloat)margin
|
|
90
|
+
{
|
|
91
|
+
NSMutableParagraphStyle *style = [_baseBlockSpacerTemplate mutableCopy];
|
|
92
|
+
style.paragraphSpacing = margin;
|
|
93
|
+
return style;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
#pragma mark - Link Registry
|
|
97
|
+
|
|
98
|
+
- (void)registerLinkRange:(NSRange)range url:(NSString *)url
|
|
99
|
+
{
|
|
100
|
+
if (range.length == 0)
|
|
101
|
+
return;
|
|
102
|
+
[self.linkRanges addObject:[NSValue valueWithRange:range]];
|
|
103
|
+
[self.linkURLs addObject:url ?: @""];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
- (void)registerHeadingRange:(NSRange)range level:(NSInteger)level text:(NSString *)text
|
|
107
|
+
{
|
|
108
|
+
if (range.length == 0)
|
|
109
|
+
return;
|
|
110
|
+
[self.headingRanges addObject:[NSValue valueWithRange:range]];
|
|
111
|
+
[self.headingLevels addObject:@(level)];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
- (void)registerImageRange:(NSRange)range altText:(NSString *)altText url:(NSString *)url
|
|
115
|
+
{
|
|
116
|
+
if (range.length == 0)
|
|
117
|
+
return;
|
|
118
|
+
[self.imageRanges addObject:[NSValue valueWithRange:range]];
|
|
119
|
+
[self.imageAltTexts addObject:altText ?: @""];
|
|
120
|
+
[self.imageURLs addObject:url ?: @""];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#pragma mark - Registration Helpers
|
|
124
|
+
|
|
125
|
+
- (void)registerListItemRange:(NSRange)range
|
|
126
|
+
position:(NSInteger)position
|
|
127
|
+
depth:(NSInteger)depth
|
|
128
|
+
isOrdered:(BOOL)isOrdered
|
|
129
|
+
{
|
|
130
|
+
if (![self isValidRange:range])
|
|
131
|
+
return;
|
|
132
|
+
|
|
133
|
+
[self.listItemRanges addObject:[NSValue valueWithRange:range]];
|
|
134
|
+
[self.listItemPositions addObject:@(position)];
|
|
135
|
+
[self.listItemDepths addObject:@(depth)];
|
|
136
|
+
[self.listItemOrdered addObject:@(isOrdered)];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
#pragma mark - Private
|
|
140
|
+
|
|
141
|
+
- (BOOL)isValidRange:(NSRange)range
|
|
142
|
+
{
|
|
143
|
+
return range.length > 0 && range.location != NSNotFound;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
#pragma mark - Block Style Management
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Updates the shared BlockStyle object with new traits.
|
|
150
|
+
* This avoids allocating a new object for every block node in the AST.
|
|
151
|
+
*/
|
|
152
|
+
- (void)setBlockStyle:(BlockType)type
|
|
153
|
+
fontSize:(CGFloat)fontSize
|
|
154
|
+
fontFamily:(NSString *)fontFamily
|
|
155
|
+
fontWeight:(NSString *)fontWeight
|
|
156
|
+
color:(UIColor *)color
|
|
157
|
+
headingLevel:(NSInteger)headingLevel
|
|
158
|
+
{
|
|
159
|
+
_currentBlockType = type;
|
|
160
|
+
_currentHeadingLevel = headingLevel;
|
|
161
|
+
|
|
162
|
+
_currentBlockStyle.fontSize = fontSize;
|
|
163
|
+
_currentBlockStyle.fontFamily = fontFamily ?: @"";
|
|
164
|
+
_currentBlockStyle.fontWeight = fontWeight ?: @"normal";
|
|
165
|
+
_currentBlockStyle.color = color ?: [UIColor blackColor];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
- (void)setBlockStyle:(BlockType)type
|
|
169
|
+
fontSize:(CGFloat)fontSize
|
|
170
|
+
fontFamily:(NSString *)fontFamily
|
|
171
|
+
fontWeight:(NSString *)fontWeight
|
|
172
|
+
color:(UIColor *)color
|
|
173
|
+
{
|
|
174
|
+
[self setBlockStyle:type fontSize:fontSize fontFamily:fontFamily fontWeight:fontWeight color:color headingLevel:0];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
- (void)setBlockStyle:(BlockType)type font:(UIFont *)font color:(UIColor *)color headingLevel:(NSInteger)headingLevel
|
|
178
|
+
{
|
|
179
|
+
_currentBlockType = type;
|
|
180
|
+
_currentHeadingLevel = headingLevel;
|
|
181
|
+
|
|
182
|
+
UIColor *finalColor = color ?: [UIColor blackColor];
|
|
183
|
+
_currentBlockStyle.cachedFont = font;
|
|
184
|
+
_currentBlockStyle.color = finalColor;
|
|
185
|
+
|
|
186
|
+
// Pre-create text attributes dictionary if we have both font and color
|
|
187
|
+
if (font) {
|
|
188
|
+
_currentBlockStyle.cachedTextAttributes =
|
|
189
|
+
@{NSFontAttributeName : font, NSForegroundColorAttributeName : finalColor};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
- (BlockStyle *)getBlockStyle
|
|
194
|
+
{
|
|
195
|
+
return _currentBlockStyle;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
- (NSDictionary *)getTextAttributes
|
|
199
|
+
{
|
|
200
|
+
BlockStyle *style = _currentBlockStyle;
|
|
201
|
+
|
|
202
|
+
// Return pre-cached attributes if available
|
|
203
|
+
if (style.cachedTextAttributes) {
|
|
204
|
+
return style.cachedTextAttributes;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Fall back to creating attributes from font cache
|
|
208
|
+
UIFont *font = style.cachedFont;
|
|
209
|
+
if (!font) {
|
|
210
|
+
font = [self cachedFontForSize:style.fontSize family:style.fontFamily weight:style.fontWeight];
|
|
211
|
+
}
|
|
212
|
+
UIColor *color = style.color ?: [UIColor blackColor];
|
|
213
|
+
|
|
214
|
+
// Cache for future calls within same block
|
|
215
|
+
style.cachedTextAttributes = @{NSFontAttributeName : font, NSForegroundColorAttributeName : color};
|
|
216
|
+
return style.cachedTextAttributes;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
- (void)clearBlockStyle
|
|
220
|
+
{
|
|
221
|
+
_currentBlockType = BlockTypeNone;
|
|
222
|
+
_currentHeadingLevel = 0;
|
|
223
|
+
_currentBlockStyle.cachedFont = nil;
|
|
224
|
+
_currentBlockStyle.cachedTextAttributes = nil;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
#pragma mark - Reset
|
|
228
|
+
|
|
229
|
+
- (void)reset
|
|
230
|
+
{
|
|
231
|
+
[_linkRanges removeAllObjects];
|
|
232
|
+
[_linkURLs removeAllObjects];
|
|
233
|
+
[_headingRanges removeAllObjects];
|
|
234
|
+
[_headingLevels removeAllObjects];
|
|
235
|
+
[_imageRanges removeAllObjects];
|
|
236
|
+
[_imageAltTexts removeAllObjects];
|
|
237
|
+
[_imageURLs removeAllObjects];
|
|
238
|
+
[_listItemRanges removeAllObjects];
|
|
239
|
+
[_listItemPositions removeAllObjects];
|
|
240
|
+
[_listItemDepths removeAllObjects];
|
|
241
|
+
[_listItemOrdered removeAllObjects];
|
|
242
|
+
[self clearBlockStyle];
|
|
243
|
+
|
|
244
|
+
_blockquoteDepth = 0;
|
|
245
|
+
_listDepth = 0;
|
|
246
|
+
_listType = ListTypeUnordered;
|
|
247
|
+
_listItemNumber = 0;
|
|
248
|
+
|
|
249
|
+
// Revert shared style object to baseline defaults
|
|
250
|
+
_currentBlockStyle.fontSize = 0;
|
|
251
|
+
_currentBlockStyle.fontFamily = @"";
|
|
252
|
+
_currentBlockStyle.fontWeight = @"";
|
|
253
|
+
_currentBlockStyle.color = [UIColor blackColor];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
#pragma mark - Static Utilities
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Determines if specific inline attributes should protect the current color.
|
|
260
|
+
*/
|
|
261
|
+
+ (BOOL)shouldPreserveColors:(NSDictionary *)attrs
|
|
262
|
+
{
|
|
263
|
+
return (attrs[NSLinkAttributeName] != nil || attrs[CodeAttributeName] != nil);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Calculates whether a strong color should override the block color.
|
|
268
|
+
*/
|
|
269
|
+
+ (UIColor *)calculateStrongColor:(UIColor *)configStrongColor blockColor:(UIColor *)blockColor
|
|
270
|
+
{
|
|
271
|
+
if (!configStrongColor || [configStrongColor isEqual:blockColor]) {
|
|
272
|
+
return blockColor;
|
|
273
|
+
}
|
|
274
|
+
return configStrongColor;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Safely calculates a range based on a start point and the current output length.
|
|
279
|
+
*/
|
|
280
|
+
+ (NSRange)rangeForRenderedContent:(NSMutableAttributedString *)output start:(NSUInteger)start
|
|
281
|
+
{
|
|
282
|
+
if (output.length < start)
|
|
283
|
+
return NSMakeRange(start, 0);
|
|
284
|
+
return NSMakeRange(start, output.length - start);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Surgically applies attributes only if they differ from current values.
|
|
289
|
+
* This minimizes "dirtying" the AttributedString, which improves layout performance.
|
|
290
|
+
*/
|
|
291
|
+
+ (void)applyFontAndColorAttributes:(NSMutableAttributedString *)output
|
|
292
|
+
range:(NSRange)range
|
|
293
|
+
font:(UIFont *)font
|
|
294
|
+
color:(UIColor *)color
|
|
295
|
+
existingAttributes:(NSDictionary *)attrs
|
|
296
|
+
shouldPreserveColors:(BOOL)shouldPreserve
|
|
297
|
+
{
|
|
298
|
+
if (range.length == 0)
|
|
299
|
+
return;
|
|
300
|
+
|
|
301
|
+
// Font Update: Only if it exists and is different
|
|
302
|
+
if (font && ![font isEqual:attrs[NSFontAttributeName]]) {
|
|
303
|
+
[output addAttribute:NSFontAttributeName value:font range:range];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Color Update: Only if not a link/code and different from existing
|
|
307
|
+
if (color && !shouldPreserve && ![color isEqual:attrs[NSForegroundColorAttributeName]]) {
|
|
308
|
+
[output addAttribute:NSForegroundColorAttributeName value:color range:range];
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
@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
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#import "RendererFactory.h"
|
|
2
|
+
#import "BlockquoteRenderer.h"
|
|
3
|
+
#import "CodeBlockRenderer.h"
|
|
4
|
+
#import "CodeRenderer.h"
|
|
5
|
+
#import "EmphasisRenderer.h"
|
|
6
|
+
#import "HeadingRenderer.h"
|
|
7
|
+
#import "ImageRenderer.h"
|
|
8
|
+
#import "LinkRenderer.h"
|
|
9
|
+
#import "ListItemRenderer.h"
|
|
10
|
+
#import "ListRenderer.h"
|
|
11
|
+
#import "ParagraphRenderer.h"
|
|
12
|
+
#import "RenderContext.h"
|
|
13
|
+
#import "StrikethroughRenderer.h"
|
|
14
|
+
#import "StrongRenderer.h"
|
|
15
|
+
#import "StyleConfig.h"
|
|
16
|
+
#import "TextRenderer.h"
|
|
17
|
+
#import "ThematicBreakRenderer.h"
|
|
18
|
+
#import "UnderlineRenderer.h"
|
|
19
|
+
|
|
20
|
+
@implementation RendererFactory {
|
|
21
|
+
StyleConfig *_config;
|
|
22
|
+
NSMutableDictionary<NSNumber *, id<NodeRenderer>> *_cache;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Initializes the factory with a shared style configuration.
|
|
27
|
+
* Uses a mutable dictionary to cache renderer instances as they are needed.
|
|
28
|
+
*/
|
|
29
|
+
- (instancetype)initWithConfig:(StyleConfig *)config
|
|
30
|
+
{
|
|
31
|
+
self = [super init];
|
|
32
|
+
if (self) {
|
|
33
|
+
_config = config;
|
|
34
|
+
_cache = [NSMutableDictionary new];
|
|
35
|
+
}
|
|
36
|
+
return self;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Returns a shared renderer instance for a specific node type.
|
|
41
|
+
* Implements lazy initialization to avoid allocating unused renderers.
|
|
42
|
+
*/
|
|
43
|
+
- (id<NodeRenderer>)rendererForNodeType:(MarkdownNodeType)type
|
|
44
|
+
{
|
|
45
|
+
id<NodeRenderer> cached = _cache[@(type)];
|
|
46
|
+
if (cached) {
|
|
47
|
+
return cached;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
id<NodeRenderer> renderer = [self createRendererForType:type];
|
|
51
|
+
if (renderer) {
|
|
52
|
+
_cache[@(type)] = renderer;
|
|
53
|
+
}
|
|
54
|
+
return renderer;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Internal factory method to instantiate specialized renderers.
|
|
59
|
+
*/
|
|
60
|
+
- (id<NodeRenderer>)createRendererForType:(MarkdownNodeType)type
|
|
61
|
+
{
|
|
62
|
+
switch (type) {
|
|
63
|
+
case MarkdownNodeTypeText:
|
|
64
|
+
return [TextRenderer new];
|
|
65
|
+
case MarkdownNodeTypeStrong:
|
|
66
|
+
return [[StrongRenderer alloc] initWithRendererFactory:self config:_config];
|
|
67
|
+
case MarkdownNodeTypeEmphasis:
|
|
68
|
+
return [[EmphasisRenderer alloc] initWithRendererFactory:self config:_config];
|
|
69
|
+
case MarkdownNodeTypeStrikethrough:
|
|
70
|
+
return [[StrikethroughRenderer alloc] initWithRendererFactory:self config:_config];
|
|
71
|
+
case MarkdownNodeTypeUnderline:
|
|
72
|
+
return [[UnderlineRenderer alloc] initWithRendererFactory:self config:_config];
|
|
73
|
+
case MarkdownNodeTypeParagraph:
|
|
74
|
+
return [[ParagraphRenderer alloc] initWithRendererFactory:self config:_config];
|
|
75
|
+
case MarkdownNodeTypeLink:
|
|
76
|
+
return [[LinkRenderer alloc] initWithRendererFactory:self config:_config];
|
|
77
|
+
case MarkdownNodeTypeHeading:
|
|
78
|
+
return [[HeadingRenderer alloc] initWithRendererFactory:self config:_config];
|
|
79
|
+
case MarkdownNodeTypeCode:
|
|
80
|
+
return [[CodeRenderer alloc] initWithRendererFactory:self config:_config];
|
|
81
|
+
case MarkdownNodeTypeImage:
|
|
82
|
+
return [[ImageRenderer alloc] initWithRendererFactory:self config:_config];
|
|
83
|
+
case MarkdownNodeTypeBlockquote:
|
|
84
|
+
return [[BlockquoteRenderer alloc] initWithRendererFactory:self config:_config];
|
|
85
|
+
case MarkdownNodeTypeListItem:
|
|
86
|
+
return [[ListItemRenderer alloc] initWithRendererFactory:self config:_config];
|
|
87
|
+
case MarkdownNodeTypeUnorderedList:
|
|
88
|
+
return [[ListRenderer alloc] initWithRendererFactory:self config:_config isOrdered:NO];
|
|
89
|
+
case MarkdownNodeTypeOrderedList:
|
|
90
|
+
return [[ListRenderer alloc] initWithRendererFactory:self config:_config isOrdered:YES];
|
|
91
|
+
case MarkdownNodeTypeCodeBlock:
|
|
92
|
+
return [[CodeBlockRenderer alloc] initWithRendererFactory:self config:_config];
|
|
93
|
+
case MarkdownNodeTypeThematicBreak:
|
|
94
|
+
return [[ThematicBreakRenderer alloc] initWithRendererFactory:self config:_config];
|
|
95
|
+
default:
|
|
96
|
+
return nil;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Helper method for container renderers to process their children.
|
|
102
|
+
* Leverages the factory to find the appropriate renderer for each child node.
|
|
103
|
+
*/
|
|
104
|
+
- (void)renderChildrenOfNode:(MarkdownASTNode *)node
|
|
105
|
+
into:(NSMutableAttributedString *)output
|
|
106
|
+
context:(RenderContext *)context
|
|
107
|
+
{
|
|
108
|
+
for (MarkdownASTNode *child in node.children) {
|
|
109
|
+
id<NodeRenderer> renderer = [self rendererForNodeType:child.type];
|
|
110
|
+
if (renderer) {
|
|
111
|
+
[renderer renderNode:child into:output context:context];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#import "StrikethroughRenderer.h"
|
|
2
|
+
#import "MarkdownASTNode.h"
|
|
3
|
+
#import "RenderContext.h"
|
|
4
|
+
#import "RendererFactory.h"
|
|
5
|
+
#import "StyleConfig.h"
|
|
6
|
+
|
|
7
|
+
@implementation StrikethroughRenderer {
|
|
8
|
+
RendererFactory *_rendererFactory;
|
|
9
|
+
StyleConfig *_config;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
- (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
|
|
13
|
+
{
|
|
14
|
+
if (self = [super init]) {
|
|
15
|
+
_rendererFactory = rendererFactory;
|
|
16
|
+
_config = (StyleConfig *)config;
|
|
17
|
+
}
|
|
18
|
+
return self;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
#pragma mark - Rendering
|
|
22
|
+
|
|
23
|
+
- (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
|
|
24
|
+
{
|
|
25
|
+
NSUInteger start = output.length;
|
|
26
|
+
[_rendererFactory renderChildrenOfNode:node into:output context:context];
|
|
27
|
+
|
|
28
|
+
NSRange range = NSMakeRange(start, output.length - start);
|
|
29
|
+
if (range.length == 0)
|
|
30
|
+
return;
|
|
31
|
+
|
|
32
|
+
// Apply strikethrough style
|
|
33
|
+
[output addAttribute:NSStrikethroughStyleAttributeName value:@(NSUnderlineStyleSingle) range:range];
|
|
34
|
+
|
|
35
|
+
// Apply strikethrough line color (not text color)
|
|
36
|
+
UIColor *strikethroughColor = [_config strikethroughColor];
|
|
37
|
+
[output addAttribute:NSStrikethroughColorAttributeName value:strikethroughColor range:range];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#import "StrongRenderer.h"
|
|
2
|
+
#import "FontUtils.h"
|
|
3
|
+
#import "MarkdownASTNode.h"
|
|
4
|
+
#import "RenderContext.h"
|
|
5
|
+
#import "RendererFactory.h"
|
|
6
|
+
#import "StyleConfig.h"
|
|
7
|
+
|
|
8
|
+
@implementation StrongRenderer {
|
|
9
|
+
RendererFactory *_rendererFactory;
|
|
10
|
+
StyleConfig *_config;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
- (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
|
|
14
|
+
{
|
|
15
|
+
if (self = [super init]) {
|
|
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
|
+
[_rendererFactory renderChildrenOfNode:node into:output context:context];
|
|
28
|
+
|
|
29
|
+
NSRange range = NSMakeRange(start, output.length - start);
|
|
30
|
+
if (range.length == 0)
|
|
31
|
+
return;
|
|
32
|
+
|
|
33
|
+
BlockStyle *blockStyle = [context getBlockStyle];
|
|
34
|
+
UIColor *configStrongColor = [_config strongColor];
|
|
35
|
+
UIColor *calculatedColor =
|
|
36
|
+
configStrongColor ? [RenderContext calculateStrongColor:configStrongColor blockColor:blockStyle.color] : nil;
|
|
37
|
+
|
|
38
|
+
[output enumerateAttributesInRange:range
|
|
39
|
+
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
|
|
40
|
+
usingBlock:^(NSDictionary<NSAttributedStringKey, id> *attrs, NSRange subrange, BOOL *stop) {
|
|
41
|
+
// 1. Resolve Font
|
|
42
|
+
UIFont *currentFont =
|
|
43
|
+
attrs[NSFontAttributeName] ?: cachedFontFromBlockStyle(blockStyle, context);
|
|
44
|
+
|
|
45
|
+
// Optimization: Only apply bold if not already bold
|
|
46
|
+
if (!([currentFont.fontDescriptor symbolicTraits] & UIFontDescriptorTraitBold)) {
|
|
47
|
+
UIFont *boldFont = [self ensureFontIsBold:currentFont];
|
|
48
|
+
if (boldFont) {
|
|
49
|
+
[output addAttribute:NSFontAttributeName value:boldFont range:subrange];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 2. Resolve Color
|
|
54
|
+
// Only apply if we have a color and the current segment doesn't explicitly forbid overrides
|
|
55
|
+
if (calculatedColor && ![RenderContext shouldPreserveColors:attrs]) {
|
|
56
|
+
// Optimization: Check if this color is already set to avoid redundant attribute changes
|
|
57
|
+
if (![attrs[NSForegroundColorAttributeName] isEqual:calculatedColor]) {
|
|
58
|
+
[output addAttribute:NSForegroundColorAttributeName
|
|
59
|
+
value:calculatedColor
|
|
60
|
+
range:subrange];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#pragma mark - Helper Methods
|
|
67
|
+
|
|
68
|
+
- (UIFont *)ensureFontIsBold:(UIFont *)font
|
|
69
|
+
{
|
|
70
|
+
if (!font)
|
|
71
|
+
return nil;
|
|
72
|
+
|
|
73
|
+
UIFontDescriptor *descriptor = font.fontDescriptor;
|
|
74
|
+
UIFontDescriptorSymbolicTraits traits = descriptor.symbolicTraits;
|
|
75
|
+
|
|
76
|
+
// Create new descriptor combining current traits with Bold
|
|
77
|
+
UIFontDescriptor *boldDescriptor = [descriptor fontDescriptorWithSymbolicTraits:(traits | UIFontDescriptorTraitBold)];
|
|
78
|
+
|
|
79
|
+
// Fallback to original font if bold version is unavailable
|
|
80
|
+
return boldDescriptor ? [UIFont fontWithDescriptor:boldDescriptor size:0] : font;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#import "TextRenderer.h"
|
|
2
|
+
#import "RenderContext.h"
|
|
3
|
+
|
|
4
|
+
@implementation TextRenderer
|
|
5
|
+
|
|
6
|
+
- (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
|
|
7
|
+
{
|
|
8
|
+
if (!node.content)
|
|
9
|
+
return;
|
|
10
|
+
|
|
11
|
+
NSAttributedString *text = [[NSAttributedString alloc] initWithString:node.content
|
|
12
|
+
attributes:[context getTextAttributes]];
|
|
13
|
+
[output appendAttributedString:text];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#import "ThematicBreakRenderer.h"
|
|
2
|
+
#import "MarkdownASTNode.h"
|
|
3
|
+
#import "StyleConfig.h"
|
|
4
|
+
#import "ThematicBreakAttachment.h"
|
|
5
|
+
|
|
6
|
+
#pragma mark - Renderer Implementation
|
|
7
|
+
|
|
8
|
+
@implementation ThematicBreakRenderer {
|
|
9
|
+
__weak id _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
|
+
- (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
|
|
24
|
+
{
|
|
25
|
+
[self ensureStartingNewline:output];
|
|
26
|
+
|
|
27
|
+
ThematicBreakAttachment *attachment = [[ThematicBreakAttachment alloc] init];
|
|
28
|
+
attachment.lineColor = _config.thematicBreakColor ?: [UIColor separatorColor];
|
|
29
|
+
attachment.lineHeight = _config.thematicBreakHeight > 0 ? _config.thematicBreakHeight : 1.0;
|
|
30
|
+
attachment.marginTop = _config.thematicBreakMarginTop;
|
|
31
|
+
attachment.marginBottom = _config.thematicBreakMarginBottom;
|
|
32
|
+
|
|
33
|
+
NSDictionary *attributes = @{
|
|
34
|
+
NSAttachmentAttributeName : attachment,
|
|
35
|
+
NSParagraphStyleAttributeName : [NSParagraphStyle defaultParagraphStyle]
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
NSAttributedString *breakString = [[NSAttributedString alloc] initWithString:@"\uFFFC" attributes:attributes];
|
|
39
|
+
|
|
40
|
+
[output appendAttributedString:breakString];
|
|
41
|
+
[output appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n"]];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#pragma mark - Private Utilities
|
|
45
|
+
|
|
46
|
+
- (void)ensureStartingNewline:(NSMutableAttributedString *)output
|
|
47
|
+
{
|
|
48
|
+
if (output.length > 0 && ![output.string hasSuffix:@"\n"]) {
|
|
49
|
+
[output appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n"]];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@end
|