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,152 @@
|
|
|
1
|
+
#import "AttributedRenderer.h"
|
|
2
|
+
#import "CodeBlockBackground.h"
|
|
3
|
+
#import "LastElementUtils.h"
|
|
4
|
+
#import "MarkdownASTNode.h"
|
|
5
|
+
#import "NodeRenderer.h"
|
|
6
|
+
#import "RenderContext.h"
|
|
7
|
+
#import "RendererFactory.h"
|
|
8
|
+
#import "StyleConfig.h"
|
|
9
|
+
|
|
10
|
+
@implementation AttributedRenderer {
|
|
11
|
+
StyleConfig *_config;
|
|
12
|
+
RendererFactory *_rendererFactory;
|
|
13
|
+
CGFloat _lastElementMarginBottom;
|
|
14
|
+
BOOL _allowTrailingMargin;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
- (instancetype)initWithConfig:(StyleConfig *)config
|
|
18
|
+
{
|
|
19
|
+
self = [super init];
|
|
20
|
+
if (self) {
|
|
21
|
+
_config = config;
|
|
22
|
+
_rendererFactory = [[RendererFactory alloc] initWithConfig:config];
|
|
23
|
+
_lastElementMarginBottom = 0.0;
|
|
24
|
+
_allowTrailingMargin = NO;
|
|
25
|
+
}
|
|
26
|
+
return self;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Entry point for rendering a Markdown AST.
|
|
31
|
+
* Sets the baseline global style and initiates the recursive traversal.
|
|
32
|
+
*/
|
|
33
|
+
- (NSMutableAttributedString *)renderRoot:(MarkdownASTNode *)root context:(RenderContext *)context
|
|
34
|
+
{
|
|
35
|
+
if (!root)
|
|
36
|
+
return [[NSMutableAttributedString alloc] init];
|
|
37
|
+
|
|
38
|
+
// 1. Establish the global baseline style.
|
|
39
|
+
// This ensures that leaf nodes (like Text) have valid attributes if they appear at the root.
|
|
40
|
+
[context setBlockStyle:BlockTypeParagraph font:_config.paragraphFont color:_config.paragraphColor headingLevel:0];
|
|
41
|
+
|
|
42
|
+
NSMutableAttributedString *output = [[NSMutableAttributedString alloc] init];
|
|
43
|
+
|
|
44
|
+
// 2. Iterate through root children.
|
|
45
|
+
// We skip the 'Root' node itself as it is a container, not a renderable element.
|
|
46
|
+
for (MarkdownASTNode *node in root.children) {
|
|
47
|
+
[self renderNodeRecursive:node into:output context:context];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 3. Remove trailing paragraph spacing from last block element
|
|
51
|
+
// Reset lastElementMarginBottom before processing
|
|
52
|
+
_lastElementMarginBottom = 0.0;
|
|
53
|
+
[self removeTrailingSpacing:output];
|
|
54
|
+
|
|
55
|
+
// 4. Cleanup global state to prevent side effects in subsequent renders.
|
|
56
|
+
[context clearBlockStyle];
|
|
57
|
+
|
|
58
|
+
return output;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Removes trailing margin spacing while preserving code block padding
|
|
62
|
+
- (void)removeTrailingSpacing:(NSMutableAttributedString *)output
|
|
63
|
+
{
|
|
64
|
+
if (output.length == 0)
|
|
65
|
+
return;
|
|
66
|
+
|
|
67
|
+
// Find the last non-newline character
|
|
68
|
+
NSRange lastContent = [output.string rangeOfCharacterFromSet:[NSCharacterSet.newlineCharacterSet invertedSet]
|
|
69
|
+
options:NSBackwardsSearch];
|
|
70
|
+
if (lastContent.location == NSNotFound)
|
|
71
|
+
return;
|
|
72
|
+
|
|
73
|
+
// 1. Capture the Margin Bottom (Scanning from last content to end)
|
|
74
|
+
_lastElementMarginBottom = 0.0;
|
|
75
|
+
for (NSUInteger i = lastContent.location; i < output.length;) {
|
|
76
|
+
NSRange attrRange;
|
|
77
|
+
NSParagraphStyle *style = [output attribute:NSParagraphStyleAttributeName atIndex:i effectiveRange:&attrRange];
|
|
78
|
+
if (style) {
|
|
79
|
+
_lastElementMarginBottom = MAX(_lastElementMarginBottom, style.paragraphSpacing);
|
|
80
|
+
}
|
|
81
|
+
i = NSMaxRange(attrRange);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 2. Trim trailing characters
|
|
85
|
+
NSUInteger logicalEnd = NSMaxRange(lastContent);
|
|
86
|
+
if (isLastElementCodeBlock(output)) {
|
|
87
|
+
NSRange codeRange;
|
|
88
|
+
[output attribute:CodeBlockAttributeName atIndex:lastContent.location effectiveRange:&codeRange];
|
|
89
|
+
logicalEnd = NSMaxRange(codeRange);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (logicalEnd < output.length) {
|
|
93
|
+
[output deleteCharactersInRange:NSMakeRange(logicalEnd, output.length - logicalEnd)];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 3. Zero out internal spacing for the last element (if not a code block)
|
|
97
|
+
if (!isLastElementCodeBlock(output)) {
|
|
98
|
+
NSRange styleRange;
|
|
99
|
+
NSParagraphStyle *style = [output attribute:NSParagraphStyleAttributeName
|
|
100
|
+
atIndex:lastContent.location
|
|
101
|
+
effectiveRange:&styleRange];
|
|
102
|
+
|
|
103
|
+
if (style) {
|
|
104
|
+
NSMutableParagraphStyle *mutableStyle = [style mutableCopy];
|
|
105
|
+
mutableStyle.paragraphSpacing = 0;
|
|
106
|
+
mutableStyle.paragraphSpacingBefore = 0;
|
|
107
|
+
|
|
108
|
+
if (isLastElementImage(output)) {
|
|
109
|
+
mutableStyle.lineSpacing = 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
NSRange safeRange = NSIntersectionRange(styleRange, NSMakeRange(0, output.length));
|
|
113
|
+
[output addAttribute:NSParagraphStyleAttributeName value:mutableStyle range:safeRange];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
- (void)setAllowTrailingMargin:(BOOL)allow
|
|
119
|
+
{
|
|
120
|
+
_allowTrailingMargin = allow;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
- (CGFloat)getLastElementMarginBottom
|
|
124
|
+
{
|
|
125
|
+
return _lastElementMarginBottom;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Orchestrates the recursive traversal of the AST.
|
|
130
|
+
* If a specialized renderer exists for a node type, it takes full control.
|
|
131
|
+
*/
|
|
132
|
+
- (void)renderNodeRecursive:(MarkdownASTNode *)node
|
|
133
|
+
into:(NSMutableAttributedString *)out
|
|
134
|
+
context:(RenderContext *)context
|
|
135
|
+
{
|
|
136
|
+
if (!node)
|
|
137
|
+
return;
|
|
138
|
+
|
|
139
|
+
id<NodeRenderer> renderer = [_rendererFactory rendererForNodeType:node.type];
|
|
140
|
+
|
|
141
|
+
if (renderer) {
|
|
142
|
+
// Specialized renderers (e.g., Strong, Link, Heading) handle their own sub-trees.
|
|
143
|
+
[renderer renderNode:node into:out context:context];
|
|
144
|
+
} else {
|
|
145
|
+
// Fallback: Default to deep-first traversal for unhandled container nodes.
|
|
146
|
+
for (MarkdownASTNode *child in node.children) {
|
|
147
|
+
[self renderNodeRecursive:child into:out context:context];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@end
|
|
@@ -0,0 +1,160 @@
|
|
|
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
|
+
NSUInteger contentStart = start;
|
|
60
|
+
if (currentDepth == 0) {
|
|
61
|
+
contentStart += applyBlockSpacingBefore(output, start, [_config blockquoteMarginTop]);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
NSRange blockquoteRange = NSMakeRange(contentStart, end - start);
|
|
65
|
+
CGFloat levelSpacing = [_config blockquoteBorderWidth] + [_config blockquoteGapWidth];
|
|
66
|
+
NSArray<NSDictionary *> *nestedInfo = [self collectNestedBlockquotes:output range:blockquoteRange depth:currentDepth];
|
|
67
|
+
|
|
68
|
+
// Apply base styling (indentation, depth, background, line height)
|
|
69
|
+
[self applyBaseBlockquoteStyle:output
|
|
70
|
+
range:blockquoteRange
|
|
71
|
+
depth:currentDepth
|
|
72
|
+
levelSpacing:levelSpacing
|
|
73
|
+
backgroundColor:[_config blockquoteBackgroundColor]
|
|
74
|
+
lineHeight:[_config blockquoteLineHeight]];
|
|
75
|
+
|
|
76
|
+
// Re-apply nested blockquote styles to restore their correct indentation
|
|
77
|
+
// (applyBaseBlockquoteStyle overwrites nested indents with the parent's indent)
|
|
78
|
+
[self reapplyNestedStyles:output nestedInfo:nestedInfo levelSpacing:levelSpacing];
|
|
79
|
+
|
|
80
|
+
if (currentDepth == 0) {
|
|
81
|
+
applyBlockSpacingAfter(output, [_config blockquoteMarginBottom]);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#pragma mark - Nested Blockquote Handling
|
|
86
|
+
|
|
87
|
+
- (NSArray<NSDictionary *> *)collectNestedBlockquotes:(NSMutableAttributedString *)output
|
|
88
|
+
range:(NSRange)blockquoteRange
|
|
89
|
+
depth:(NSInteger)currentDepth
|
|
90
|
+
{
|
|
91
|
+
NSMutableArray<NSDictionary *> *nestedInfo = [NSMutableArray array];
|
|
92
|
+
|
|
93
|
+
[output
|
|
94
|
+
enumerateAttribute:BlockquoteDepthAttributeName
|
|
95
|
+
inRange:blockquoteRange
|
|
96
|
+
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
|
|
97
|
+
usingBlock:^(id value, NSRange range, BOOL *stop) {
|
|
98
|
+
NSInteger depth = [value integerValue];
|
|
99
|
+
if (value && depth > currentDepth) {
|
|
100
|
+
[nestedInfo
|
|
101
|
+
addObject:@{kNestedInfoDepthKey : value, kNestedInfoRangeKey : [NSValue valueWithRange:range]}];
|
|
102
|
+
}
|
|
103
|
+
}];
|
|
104
|
+
|
|
105
|
+
return nestedInfo;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
- (void)applyBaseBlockquoteStyle:(NSMutableAttributedString *)output
|
|
109
|
+
range:(NSRange)blockquoteRange
|
|
110
|
+
depth:(NSInteger)currentDepth
|
|
111
|
+
levelSpacing:(CGFloat)levelSpacing
|
|
112
|
+
backgroundColor:(UIColor *)backgroundColor
|
|
113
|
+
lineHeight:(CGFloat)lineHeight
|
|
114
|
+
{
|
|
115
|
+
NSMutableParagraphStyle *paragraphStyle = getOrCreateParagraphStyle(output, blockquoteRange.location);
|
|
116
|
+
CGFloat totalIndent = [self calculateIndentForDepth:currentDepth levelSpacing:levelSpacing];
|
|
117
|
+
paragraphStyle.firstLineHeadIndent = totalIndent;
|
|
118
|
+
paragraphStyle.headIndent = totalIndent;
|
|
119
|
+
|
|
120
|
+
NSMutableDictionary *newAttributes =
|
|
121
|
+
[NSMutableDictionary dictionaryWithObjectsAndKeys:paragraphStyle, NSParagraphStyleAttributeName, @(currentDepth),
|
|
122
|
+
BlockquoteDepthAttributeName, nil];
|
|
123
|
+
if (backgroundColor) {
|
|
124
|
+
newAttributes[BlockquoteBackgroundColorAttributeName] = backgroundColor;
|
|
125
|
+
}
|
|
126
|
+
[output addAttributes:newAttributes range:blockquoteRange];
|
|
127
|
+
|
|
128
|
+
applyLineHeight(output, blockquoteRange, lineHeight);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
- (void)reapplyNestedStyles:(NSMutableAttributedString *)output
|
|
132
|
+
nestedInfo:(NSArray<NSDictionary *> *)nestedInfo
|
|
133
|
+
levelSpacing:(CGFloat)levelSpacing
|
|
134
|
+
{
|
|
135
|
+
// Re-apply indentation to nested blockquotes since applyBaseBlockquoteStyle
|
|
136
|
+
// overwrote them with the parent's indentation
|
|
137
|
+
for (NSDictionary *info in nestedInfo) {
|
|
138
|
+
NSRange nestedRange = [info[kNestedInfoRangeKey] rangeValue];
|
|
139
|
+
NSInteger nestedDepth = [info[kNestedInfoDepthKey] integerValue];
|
|
140
|
+
NSMutableParagraphStyle *style = getOrCreateParagraphStyle(output, nestedRange.location);
|
|
141
|
+
|
|
142
|
+
CGFloat indent = [self calculateIndentForDepth:nestedDepth levelSpacing:levelSpacing];
|
|
143
|
+
style.firstLineHeadIndent = indent;
|
|
144
|
+
style.headIndent = indent;
|
|
145
|
+
style.tailIndent = 0;
|
|
146
|
+
|
|
147
|
+
[output
|
|
148
|
+
addAttributes:@{NSParagraphStyleAttributeName : style, BlockquoteDepthAttributeName : info[kNestedInfoDepthKey]}
|
|
149
|
+
range:nestedRange];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#pragma mark - Helper Methods
|
|
154
|
+
|
|
155
|
+
- (CGFloat)calculateIndentForDepth:(NSInteger)depth levelSpacing:(CGFloat)levelSpacing
|
|
156
|
+
{
|
|
157
|
+
return (depth + 1) * levelSpacing;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@end
|
|
@@ -0,0 +1,90 @@
|
|
|
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 marginTop = [_config codeBlockMarginTop];
|
|
31
|
+
CGFloat marginBottom = [_config codeBlockMarginBottom];
|
|
32
|
+
|
|
33
|
+
NSUInteger blockStart = output.length;
|
|
34
|
+
blockStart += applyBlockSpacingBefore(output, blockStart, marginTop);
|
|
35
|
+
|
|
36
|
+
// Top Padding: Inserted as a spacer character inside the background area
|
|
37
|
+
[output appendAttributedString:kNewlineAttributedString];
|
|
38
|
+
NSMutableParagraphStyle *topSpacerStyle = [context spacerStyleWithHeight:padding spacing:0];
|
|
39
|
+
[output addAttribute:NSParagraphStyleAttributeName value:topSpacerStyle range:NSMakeRange(blockStart, 1)];
|
|
40
|
+
|
|
41
|
+
NSUInteger contentStart = output.length;
|
|
42
|
+
@try {
|
|
43
|
+
[_rendererFactory renderChildrenOfNode:node into:output context:context];
|
|
44
|
+
} @finally {
|
|
45
|
+
[context clearBlockStyle];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
NSUInteger contentEnd = output.length;
|
|
49
|
+
if (contentEnd <= contentStart)
|
|
50
|
+
return;
|
|
51
|
+
|
|
52
|
+
NSRange contentRange = NSMakeRange(contentStart, contentEnd - contentStart);
|
|
53
|
+
|
|
54
|
+
UIFont *codeFont = [_config codeBlockFont];
|
|
55
|
+
UIColor *codeColor = [_config codeBlockColor];
|
|
56
|
+
if (codeColor) {
|
|
57
|
+
[output addAttributes:@{NSFontAttributeName : codeFont, NSForegroundColorAttributeName : codeColor}
|
|
58
|
+
range:contentRange];
|
|
59
|
+
} else {
|
|
60
|
+
[output addAttribute:NSFontAttributeName value:codeFont range:contentRange];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (lineHeight > 0) {
|
|
64
|
+
applyLineHeight(output, contentRange, lineHeight);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Apply horizontal indents to the content
|
|
68
|
+
NSMutableParagraphStyle *baseStyle = [getOrCreateParagraphStyle(output, contentStart) mutableCopy];
|
|
69
|
+
baseStyle.firstLineHeadIndent = padding;
|
|
70
|
+
baseStyle.headIndent = padding;
|
|
71
|
+
baseStyle.tailIndent = -padding;
|
|
72
|
+
[output addAttribute:NSParagraphStyleAttributeName value:baseStyle range:contentRange];
|
|
73
|
+
|
|
74
|
+
// Bottom Padding: Inserted as a spacer character inside the background area
|
|
75
|
+
NSUInteger bottomPaddingStart = output.length;
|
|
76
|
+
[output appendAttributedString:kNewlineAttributedString];
|
|
77
|
+
NSMutableParagraphStyle *bottomPaddingStyle = [context spacerStyleWithHeight:padding spacing:0];
|
|
78
|
+
[output addAttribute:NSParagraphStyleAttributeName value:bottomPaddingStyle range:NSMakeRange(bottomPaddingStart, 1)];
|
|
79
|
+
|
|
80
|
+
// Define the range for background rendering (includes padding, excludes margins)
|
|
81
|
+
NSRange backgroundRange = NSMakeRange(blockStart, output.length - blockStart);
|
|
82
|
+
[output addAttribute:CodeBlockAttributeName value:@YES range:backgroundRange];
|
|
83
|
+
|
|
84
|
+
// External Margin: Applied outside the background range
|
|
85
|
+
if (marginBottom > 0) {
|
|
86
|
+
applyBlockSpacingAfter(output, marginBottom);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@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
|