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,105 @@
|
|
|
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 marginTop;
|
|
14
|
+
CGFloat marginBottom;
|
|
15
|
+
CGFloat lineHeight;
|
|
16
|
+
NSTextAlignment textAlign;
|
|
17
|
+
} HeadingStyle;
|
|
18
|
+
|
|
19
|
+
static NSString *const kHeadingTypes[] = {nil, @"heading-1", @"heading-2", @"heading-3",
|
|
20
|
+
@"heading-4", @"heading-5", @"heading-6"};
|
|
21
|
+
|
|
22
|
+
@implementation HeadingRenderer {
|
|
23
|
+
RendererFactory *_rendererFactory;
|
|
24
|
+
StyleConfig *_config;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
- (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
|
|
28
|
+
{
|
|
29
|
+
if (self = [super init]) {
|
|
30
|
+
_rendererFactory = rendererFactory;
|
|
31
|
+
_config = (StyleConfig *)config;
|
|
32
|
+
}
|
|
33
|
+
return self;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
- (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
|
|
37
|
+
{
|
|
38
|
+
NSInteger level = [node.attributes[@"level"] integerValue];
|
|
39
|
+
if (level < 1 || level > 6)
|
|
40
|
+
level = 1;
|
|
41
|
+
|
|
42
|
+
HeadingStyle style = [self styleForLevel:level];
|
|
43
|
+
[context setBlockStyle:BlockTypeHeading font:style.font color:style.color headingLevel:level];
|
|
44
|
+
|
|
45
|
+
NSUInteger start = output.length;
|
|
46
|
+
NSUInteger contentStart = start;
|
|
47
|
+
|
|
48
|
+
// Spacing at the very start of the document requires a spacer character (index 0 check)
|
|
49
|
+
if (start == 0) {
|
|
50
|
+
NSUInteger offset = applyBlockSpacingBefore(output, 0, style.marginTop);
|
|
51
|
+
contentStart += offset;
|
|
52
|
+
start += offset;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@try {
|
|
56
|
+
[_rendererFactory renderChildrenOfNode:node into:output context:context];
|
|
57
|
+
} @finally {
|
|
58
|
+
[context clearBlockStyle];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
NSRange range = NSMakeRange(start, output.length - start);
|
|
62
|
+
if (range.length == 0)
|
|
63
|
+
return;
|
|
64
|
+
|
|
65
|
+
// Register heading for accessibility
|
|
66
|
+
NSString *headingText = [[output attributedSubstringFromRange:range] string];
|
|
67
|
+
[context registerHeadingRange:range level:level text:headingText];
|
|
68
|
+
|
|
69
|
+
// Metadata attribute used for post-processing (e.g., Export to Markdown/HTML)
|
|
70
|
+
[output addAttribute:MarkdownTypeAttributeName value:kHeadingTypes[level] range:range];
|
|
71
|
+
|
|
72
|
+
applyLineHeight(output, range, style.lineHeight);
|
|
73
|
+
applyTextAlignment(output, range, style.textAlign);
|
|
74
|
+
|
|
75
|
+
// Use paragraphSpacingBefore for internal elements; applyBlockSpacingBefore handles index 0
|
|
76
|
+
if (contentStart != 1) {
|
|
77
|
+
applyParagraphSpacingBefore(output, range, style.marginTop);
|
|
78
|
+
}
|
|
79
|
+
applyParagraphSpacingAfter(output, start, style.marginBottom);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#pragma mark - Style Mapping
|
|
83
|
+
|
|
84
|
+
- (HeadingStyle)styleForLevel:(NSInteger)level
|
|
85
|
+
{
|
|
86
|
+
StyleConfig *c = _config;
|
|
87
|
+
switch (level) {
|
|
88
|
+
case 1:
|
|
89
|
+
return (HeadingStyle){c.h1Font, c.h1Color, c.h1MarginTop, c.h1MarginBottom, c.h1LineHeight, c.h1TextAlign};
|
|
90
|
+
case 2:
|
|
91
|
+
return (HeadingStyle){c.h2Font, c.h2Color, c.h2MarginTop, c.h2MarginBottom, c.h2LineHeight, c.h2TextAlign};
|
|
92
|
+
case 3:
|
|
93
|
+
return (HeadingStyle){c.h3Font, c.h3Color, c.h3MarginTop, c.h3MarginBottom, c.h3LineHeight, c.h3TextAlign};
|
|
94
|
+
case 4:
|
|
95
|
+
return (HeadingStyle){c.h4Font, c.h4Color, c.h4MarginTop, c.h4MarginBottom, c.h4LineHeight, c.h4TextAlign};
|
|
96
|
+
case 5:
|
|
97
|
+
return (HeadingStyle){c.h5Font, c.h5Color, c.h5MarginTop, c.h5MarginBottom, c.h5LineHeight, c.h5TextAlign};
|
|
98
|
+
case 6:
|
|
99
|
+
return (HeadingStyle){c.h6Font, c.h6Color, c.h6MarginTop, c.h6MarginBottom, c.h6LineHeight, c.h6TextAlign};
|
|
100
|
+
default:
|
|
101
|
+
return [self styleForLevel:1];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#import "ImageRenderer.h"
|
|
2
|
+
#import "EnrichedMarkdownImageAttachment.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
|
+
if (!imageURL || imageURL.length == 0) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
BOOL isInline = [self isInlineImageInOutput:output];
|
|
35
|
+
EnrichedMarkdownImageAttachment *attachment = [[EnrichedMarkdownImageAttachment alloc] initWithImageURL:imageURL
|
|
36
|
+
config:_config
|
|
37
|
+
isInline:isInline];
|
|
38
|
+
|
|
39
|
+
NSUInteger startIndex = output.length;
|
|
40
|
+
|
|
41
|
+
NSAttributedString *imageString = [NSAttributedString attributedStringWithAttachment:attachment];
|
|
42
|
+
[output appendAttributedString:imageString];
|
|
43
|
+
|
|
44
|
+
// Extract alt text from children ( - "alt text" is in children)
|
|
45
|
+
NSString *altText = [self extractTextFromNode:node];
|
|
46
|
+
NSRange imageRange = NSMakeRange(startIndex, output.length - startIndex);
|
|
47
|
+
[context registerImageRange:imageRange altText:altText url:imageURL];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#pragma mark - Private Helpers
|
|
51
|
+
|
|
52
|
+
- (NSString *)extractTextFromNode:(MarkdownASTNode *)node
|
|
53
|
+
{
|
|
54
|
+
if (!node)
|
|
55
|
+
return @"";
|
|
56
|
+
|
|
57
|
+
NSMutableString *buffer = [NSMutableString string];
|
|
58
|
+
[self _appendChildTextFromNode:node toBuffer:buffer];
|
|
59
|
+
return [buffer copy];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
- (void)_appendChildTextFromNode:(MarkdownASTNode *)node toBuffer:(NSMutableString *)buffer
|
|
63
|
+
{
|
|
64
|
+
if (node.content.length > 0) {
|
|
65
|
+
[buffer appendString:node.content];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (MarkdownASTNode *child in node.children) {
|
|
69
|
+
[self _appendChildTextFromNode:child toBuffer:buffer];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
- (BOOL)isInlineImageInOutput:(NSAttributedString *)output
|
|
74
|
+
{
|
|
75
|
+
if (output.length == 0) {
|
|
76
|
+
return NO;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
unichar lastChar = [output.string characterAtIndex:output.length - 1];
|
|
80
|
+
return (lastChar != kLineBreak && lastChar != kZeroWidthSpace);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#import "LinkRenderer.h"
|
|
2
|
+
#import "FontUtils.h"
|
|
3
|
+
#import "RenderContext.h"
|
|
4
|
+
#import "RendererFactory.h"
|
|
5
|
+
#import "StyleConfig.h"
|
|
6
|
+
|
|
7
|
+
@implementation LinkRenderer {
|
|
8
|
+
RendererFactory *_rendererFactory;
|
|
9
|
+
StyleConfig *_config;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
- (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
|
|
13
|
+
{
|
|
14
|
+
self = [super init];
|
|
15
|
+
if (self) {
|
|
16
|
+
_rendererFactory = rendererFactory;
|
|
17
|
+
_config = (StyleConfig *)config;
|
|
18
|
+
}
|
|
19
|
+
return self;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#pragma mark - Rendering
|
|
23
|
+
|
|
24
|
+
- (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
|
|
25
|
+
{
|
|
26
|
+
NSUInteger start = output.length;
|
|
27
|
+
|
|
28
|
+
// 1. Render children first to establish base attributes
|
|
29
|
+
[_rendererFactory renderChildrenOfNode:node into:output context:context];
|
|
30
|
+
|
|
31
|
+
NSRange range = NSMakeRange(start, output.length - start);
|
|
32
|
+
if (range.length == 0)
|
|
33
|
+
return;
|
|
34
|
+
|
|
35
|
+
// 2. Extract configuration
|
|
36
|
+
NSString *url = node.attributes[@"url"] ?: @"";
|
|
37
|
+
UIColor *linkColor = [_config linkColor];
|
|
38
|
+
NSNumber *underlineStyle = @([_config linkUnderline] ? NSUnderlineStyleSingle : NSUnderlineStyleNone);
|
|
39
|
+
|
|
40
|
+
// 3. Apply core link functionality (non-destructive)
|
|
41
|
+
[output addAttribute:NSLinkAttributeName value:url range:range];
|
|
42
|
+
|
|
43
|
+
// 4. Optimize visual attributes via enumeration to avoid redundant updates
|
|
44
|
+
[output enumerateAttributesInRange:range
|
|
45
|
+
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
|
|
46
|
+
usingBlock:^(NSDictionary<NSAttributedStringKey, id> *attrs, NSRange subrange, BOOL *stop) {
|
|
47
|
+
NSMutableDictionary *newAttributes = [NSMutableDictionary dictionary];
|
|
48
|
+
|
|
49
|
+
// Only apply link color if the subrange isn't already colored by the link style
|
|
50
|
+
if (linkColor && ![attrs[NSForegroundColorAttributeName] isEqual:linkColor]) {
|
|
51
|
+
newAttributes[NSForegroundColorAttributeName] = linkColor;
|
|
52
|
+
newAttributes[NSUnderlineColorAttributeName] = linkColor;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Only update underline style if it differs from the config
|
|
56
|
+
if (![attrs[NSUnderlineStyleAttributeName] isEqual:underlineStyle]) {
|
|
57
|
+
newAttributes[NSUnderlineStyleAttributeName] = underlineStyle;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (newAttributes.count > 0) {
|
|
61
|
+
[output addAttributes:newAttributes range:subrange];
|
|
62
|
+
}
|
|
63
|
+
}];
|
|
64
|
+
|
|
65
|
+
// 5. Register for touch handling
|
|
66
|
+
[context registerLinkRange:range url:url];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#import "NodeRenderer.h"
|
|
2
|
+
|
|
3
|
+
@class RendererFactory;
|
|
4
|
+
@class StyleConfig;
|
|
5
|
+
@class RenderContext;
|
|
6
|
+
|
|
7
|
+
// Attribute names for list styling
|
|
8
|
+
extern NSString *const ListDepthAttribute;
|
|
9
|
+
extern NSString *const ListTypeAttribute;
|
|
10
|
+
extern NSString *const ListItemNumberAttribute;
|
|
11
|
+
|
|
12
|
+
@interface ListItemRenderer : NSObject <NodeRenderer>
|
|
13
|
+
|
|
14
|
+
- (instancetype)initWithRendererFactory:(RendererFactory *)rendererFactory config:(StyleConfig *)config;
|
|
15
|
+
|
|
16
|
+
@end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#import "ListItemRenderer.h"
|
|
2
|
+
#import "MarkdownASTNode.h"
|
|
3
|
+
#import "ParagraphStyleUtils.h"
|
|
4
|
+
#import "RenderContext.h"
|
|
5
|
+
#import "RendererFactory.h"
|
|
6
|
+
#import "StyleConfig.h"
|
|
7
|
+
|
|
8
|
+
NSString *const ListDepthAttribute = @"ListDepth";
|
|
9
|
+
NSString *const ListTypeAttribute = @"ListType";
|
|
10
|
+
NSString *const ListItemNumberAttribute = @"ListItemNumber";
|
|
11
|
+
|
|
12
|
+
@implementation ListItemRenderer {
|
|
13
|
+
RendererFactory *_rendererFactory;
|
|
14
|
+
StyleConfig *_config;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
- (instancetype)initWithRendererFactory:(RendererFactory *)factory config:(StyleConfig *)config
|
|
18
|
+
{
|
|
19
|
+
if (self = [super init]) {
|
|
20
|
+
_rendererFactory = factory;
|
|
21
|
+
_config = config;
|
|
22
|
+
}
|
|
23
|
+
return self;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
- (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
|
|
27
|
+
{
|
|
28
|
+
if (!context)
|
|
29
|
+
return;
|
|
30
|
+
|
|
31
|
+
context.listItemNumber++;
|
|
32
|
+
const NSInteger currentPosition = context.listItemNumber;
|
|
33
|
+
const NSInteger currentDepth = context.listDepth; // 1-based (1 = top level)
|
|
34
|
+
|
|
35
|
+
const NSUInteger startLocation = output.length;
|
|
36
|
+
|
|
37
|
+
// Render the actual content of the list item (text, bolding, etc.)
|
|
38
|
+
[_rendererFactory renderChildrenOfNode:node into:output context:context];
|
|
39
|
+
|
|
40
|
+
// Ensure every list item ends with a newline to prevent paragraph merging
|
|
41
|
+
if (output.length > startLocation && ![output.string hasSuffix:@"\n"]) {
|
|
42
|
+
[output appendAttributedString:kNewlineAttributedString];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const NSRange itemRange = NSMakeRange(startLocation, output.length - startLocation);
|
|
46
|
+
if (itemRange.length == 0)
|
|
47
|
+
return;
|
|
48
|
+
|
|
49
|
+
// Informs MarkdownAccessibilityElementBuilder about the specific boundaries of this list item
|
|
50
|
+
[context registerListItemRange:itemRange
|
|
51
|
+
position:currentPosition
|
|
52
|
+
depth:currentDepth
|
|
53
|
+
isOrdered:(context.listType == ListTypeOrdered)];
|
|
54
|
+
|
|
55
|
+
// currentDepth - 1 handles the horizontal offset for nested lists
|
|
56
|
+
const NSInteger nestingLevel = currentDepth - 1;
|
|
57
|
+
const CGFloat baseMarkerWidth = (context.listType == ListTypeOrdered) ? [_config effectiveListMarginLeftForNumber]
|
|
58
|
+
: [_config effectiveListMarginLeftForBullet];
|
|
59
|
+
|
|
60
|
+
const CGFloat totalIndent =
|
|
61
|
+
baseMarkerWidth + [_config effectiveListGapWidth] + (nestingLevel * [_config listStyleMarginLeft]);
|
|
62
|
+
|
|
63
|
+
const CGFloat lineHeightConfig = [_config listStyleLineHeight];
|
|
64
|
+
|
|
65
|
+
// Boxing metadata for attributed string storage
|
|
66
|
+
NSDictionary *metadata = @{
|
|
67
|
+
ListDepthAttribute : @(nestingLevel),
|
|
68
|
+
ListTypeAttribute : @(context.listType),
|
|
69
|
+
ListItemNumberAttribute : @(currentPosition)
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// We enumerate to ensure we don't overwrite styles of nested sub-lists
|
|
73
|
+
// that may have already been rendered inside this item.
|
|
74
|
+
[output enumerateAttribute:ListDepthAttribute
|
|
75
|
+
inRange:itemRange
|
|
76
|
+
options:0
|
|
77
|
+
usingBlock:^(id depthAttr, NSRange range, BOOL *stop) {
|
|
78
|
+
// If a segment already has a Depth attribute higher than our current level,
|
|
79
|
+
// it belongs to a nested list and we should skip it to preserve its styling.
|
|
80
|
+
if (depthAttr && [depthAttr integerValue] > nestingLevel) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
|
|
85
|
+
style.firstLineHeadIndent = totalIndent;
|
|
86
|
+
style.headIndent = totalIndent;
|
|
87
|
+
|
|
88
|
+
// Apply line height if configured
|
|
89
|
+
UIFont *currentFont = [output attribute:NSFontAttributeName
|
|
90
|
+
atIndex:range.location
|
|
91
|
+
effectiveRange:NULL];
|
|
92
|
+
if (lineHeightConfig > 0 && currentFont) {
|
|
93
|
+
style.lineHeightMultiple = lineHeightConfig / currentFont.pointSize;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
NSMutableDictionary *attributesToApply = [metadata mutableCopy];
|
|
97
|
+
attributesToApply[NSParagraphStyleAttributeName] = style;
|
|
98
|
+
|
|
99
|
+
[output addAttributes:attributesToApply range:range];
|
|
100
|
+
}];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#import "NodeRenderer.h"
|
|
2
|
+
|
|
3
|
+
@class RendererFactory;
|
|
4
|
+
@class StyleConfig;
|
|
5
|
+
@class RenderContext;
|
|
6
|
+
|
|
7
|
+
@interface ListRenderer : NSObject <NodeRenderer>
|
|
8
|
+
|
|
9
|
+
- (instancetype)initWithRendererFactory:(RendererFactory *)rendererFactory
|
|
10
|
+
config:(StyleConfig *)config
|
|
11
|
+
isOrdered:(BOOL)isOrdered;
|
|
12
|
+
|
|
13
|
+
@end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#import "ListRenderer.h"
|
|
2
|
+
#import "BlockquoteRenderer.h"
|
|
3
|
+
#import "MarkdownASTNode.h"
|
|
4
|
+
#import "ParagraphStyleUtils.h"
|
|
5
|
+
#import "RenderContext.h"
|
|
6
|
+
#import "RendererFactory.h"
|
|
7
|
+
#import "StyleConfig.h"
|
|
8
|
+
|
|
9
|
+
@implementation ListRenderer {
|
|
10
|
+
RendererFactory *_rendererFactory;
|
|
11
|
+
StyleConfig *_config;
|
|
12
|
+
BOOL _isOrdered;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
- (instancetype)initWithRendererFactory:(RendererFactory *)factory config:(StyleConfig *)config isOrdered:(BOOL)ordered
|
|
16
|
+
{
|
|
17
|
+
if (self = [super init]) {
|
|
18
|
+
_rendererFactory = factory;
|
|
19
|
+
_config = config;
|
|
20
|
+
_isOrdered = ordered;
|
|
21
|
+
}
|
|
22
|
+
return self;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
- (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
|
|
26
|
+
{
|
|
27
|
+
if (!context)
|
|
28
|
+
return;
|
|
29
|
+
|
|
30
|
+
const NSInteger prevDepth = context.listDepth;
|
|
31
|
+
const ListType prevType = context.listType;
|
|
32
|
+
const NSInteger prevNum = context.listItemNumber;
|
|
33
|
+
|
|
34
|
+
const NSUInteger startLocation = output.length;
|
|
35
|
+
NSUInteger contentStart = startLocation;
|
|
36
|
+
|
|
37
|
+
if (prevDepth == 0) {
|
|
38
|
+
// Apply top margin for root-level list
|
|
39
|
+
contentStart += applyBlockSpacingBefore(output, startLocation, _config.listStyleMarginTop);
|
|
40
|
+
} else if (output.length > 0 && ![output.string hasSuffix:@"\n"]) {
|
|
41
|
+
// Ensure nested lists start on a new line
|
|
42
|
+
[output appendAttributedString:kNewlineAttributedString];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
context.listDepth = prevDepth + 1;
|
|
46
|
+
context.listType = _isOrdered ? ListTypeOrdered : ListTypeUnordered;
|
|
47
|
+
context.listItemNumber = 0; // Reset counter for this specific list level
|
|
48
|
+
|
|
49
|
+
[context setBlockStyle:_isOrdered ? BlockTypeOrderedList : BlockTypeUnorderedList
|
|
50
|
+
font:_config.listStyleFont
|
|
51
|
+
color:_config.listStyleColor
|
|
52
|
+
headingLevel:0];
|
|
53
|
+
|
|
54
|
+
@try {
|
|
55
|
+
[_rendererFactory renderChildrenOfNode:node into:output context:context];
|
|
56
|
+
} @finally {
|
|
57
|
+
context.listDepth = prevDepth;
|
|
58
|
+
context.listType = prevType;
|
|
59
|
+
context.listItemNumber = prevNum;
|
|
60
|
+
|
|
61
|
+
if (prevDepth == 0) {
|
|
62
|
+
[context clearBlockStyle];
|
|
63
|
+
|
|
64
|
+
// Apply bottom margin for root-level list
|
|
65
|
+
applyBlockSpacingAfter(output, _config.listStyleMarginBottom);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#import "ParagraphRenderer.h"
|
|
2
|
+
#import "FontUtils.h"
|
|
3
|
+
#import "MarkdownASTNode.h"
|
|
4
|
+
#import "ParagraphStyleUtils.h"
|
|
5
|
+
#import "RendererFactory.h"
|
|
6
|
+
#import "StyleConfig.h"
|
|
7
|
+
|
|
8
|
+
@implementation ParagraphRenderer {
|
|
9
|
+
RendererFactory *_rendererFactory;
|
|
10
|
+
StyleConfig *_config;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
- (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config
|
|
14
|
+
{
|
|
15
|
+
self = [super init];
|
|
16
|
+
if (self) {
|
|
17
|
+
_rendererFactory = rendererFactory;
|
|
18
|
+
_config = (StyleConfig *)config;
|
|
19
|
+
}
|
|
20
|
+
return self;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
- (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context
|
|
24
|
+
{
|
|
25
|
+
// Only set block style if a parent element (e.g. List, Blockquote) hasn't already established one
|
|
26
|
+
BOOL isTopLevel = (context.currentBlockType == BlockTypeNone);
|
|
27
|
+
|
|
28
|
+
if (isTopLevel) {
|
|
29
|
+
[context setBlockStyle:BlockTypeParagraph font:_config.paragraphFont color:_config.paragraphColor headingLevel:0];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
NSUInteger start = output.length;
|
|
33
|
+
BOOL shouldApplyMargin =
|
|
34
|
+
(context.currentBlockType == BlockTypeNone || context.currentBlockType == BlockTypeParagraph);
|
|
35
|
+
|
|
36
|
+
// Detect if the paragraph is a wrapper for a standalone image to use image-specific spacing
|
|
37
|
+
BOOL isBlockImage = (node.children.count == 1 && ((MarkdownASTNode *)node.children[0]).type == MarkdownNodeTypeImage);
|
|
38
|
+
CGFloat marginTop = isBlockImage ? _config.imageMarginTop : _config.paragraphMarginTop;
|
|
39
|
+
|
|
40
|
+
NSUInteger contentStart = start;
|
|
41
|
+
|
|
42
|
+
// Handle leading margin for the first element in the document (Index 0 check)
|
|
43
|
+
if (shouldApplyMargin && start == 0) {
|
|
44
|
+
NSUInteger offset = applyBlockSpacingBefore(output, 0, marginTop);
|
|
45
|
+
contentStart += offset;
|
|
46
|
+
start += offset;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@try {
|
|
50
|
+
[_rendererFactory renderChildrenOfNode:node into:output context:context];
|
|
51
|
+
} @finally {
|
|
52
|
+
if (isTopLevel) {
|
|
53
|
+
[context clearBlockStyle];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (output.length <= start)
|
|
58
|
+
return;
|
|
59
|
+
NSRange range = NSMakeRange(start, output.length - start);
|
|
60
|
+
|
|
61
|
+
// Avoid standard line height on block images to prevent vertical alignment issues
|
|
62
|
+
if (!isBlockImage) {
|
|
63
|
+
applyLineHeight(output, range, _config.paragraphLineHeight);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
applyTextAlignment(output, range, _config.paragraphTextAlign);
|
|
67
|
+
|
|
68
|
+
// Apply marginTop for non-index-0 elements using paragraphSpacingBefore
|
|
69
|
+
if (shouldApplyMargin && contentStart != 1) {
|
|
70
|
+
applyParagraphSpacingBefore(output, range, marginTop);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
CGFloat marginBottom = 0;
|
|
74
|
+
if (shouldApplyMargin) {
|
|
75
|
+
marginBottom = isBlockImage ? _config.imageMarginBottom : _config.paragraphMarginBottom;
|
|
76
|
+
}
|
|
77
|
+
applyParagraphSpacingAfter(output, start, marginBottom);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
#import <UIKit/UIKit.h>
|
|
3
|
+
|
|
4
|
+
typedef NS_ENUM(NSInteger, BlockType) {
|
|
5
|
+
BlockTypeNone,
|
|
6
|
+
BlockTypeParagraph,
|
|
7
|
+
BlockTypeHeading,
|
|
8
|
+
BlockTypeBlockquote,
|
|
9
|
+
BlockTypeUnorderedList,
|
|
10
|
+
BlockTypeOrderedList,
|
|
11
|
+
BlockTypeCodeBlock
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
typedef NS_ENUM(NSInteger, ListType) { ListTypeUnordered, ListTypeOrdered };
|
|
15
|
+
|
|
16
|
+
@interface BlockStyle : NSObject
|
|
17
|
+
@property (nonatomic, assign) CGFloat fontSize;
|
|
18
|
+
@property (nonatomic, strong) NSString *fontFamily;
|
|
19
|
+
@property (nonatomic, strong) NSString *fontWeight;
|
|
20
|
+
@property (nonatomic, strong) UIColor *color;
|
|
21
|
+
@property (nonatomic, strong) UIFont *cachedFont;
|
|
22
|
+
@property (nonatomic, strong) NSDictionary *cachedTextAttributes;
|
|
23
|
+
@end
|
|
24
|
+
|
|
25
|
+
@interface RenderContext : NSObject
|
|
26
|
+
@property (nonatomic, strong) NSMutableArray<NSValue *> *linkRanges;
|
|
27
|
+
@property (nonatomic, strong) NSMutableArray<NSString *> *linkURLs;
|
|
28
|
+
@property (nonatomic, strong) NSMutableArray<NSValue *> *headingRanges;
|
|
29
|
+
@property (nonatomic, strong) NSMutableArray<NSNumber *> *headingLevels;
|
|
30
|
+
@property (nonatomic, strong) NSMutableArray<NSValue *> *imageRanges;
|
|
31
|
+
@property (nonatomic, strong) NSMutableArray<NSString *> *imageAltTexts;
|
|
32
|
+
@property (nonatomic, strong) NSMutableArray<NSString *> *imageURLs;
|
|
33
|
+
@property (nonatomic, strong) NSMutableArray<NSValue *> *listItemRanges;
|
|
34
|
+
@property (nonatomic, strong) NSMutableArray<NSNumber *> *listItemPositions; // Position in parent list (1, 2, 3...)
|
|
35
|
+
@property (nonatomic, strong) NSMutableArray<NSNumber *> *listItemDepths; // Nesting depth (1 = top level, 2+ = nested)
|
|
36
|
+
@property (nonatomic, strong) NSMutableArray<NSNumber *> *listItemOrdered; // YES = ordered, NO = unordered (bullet)
|
|
37
|
+
@property (nonatomic, assign) BlockType currentBlockType;
|
|
38
|
+
@property (nonatomic, strong) BlockStyle *currentBlockStyle;
|
|
39
|
+
@property (nonatomic, assign) NSInteger currentHeadingLevel;
|
|
40
|
+
@property (nonatomic, assign) NSInteger blockquoteDepth;
|
|
41
|
+
@property (nonatomic, assign) NSInteger listDepth;
|
|
42
|
+
@property (nonatomic, assign) ListType listType;
|
|
43
|
+
@property (nonatomic, assign) NSInteger listItemNumber;
|
|
44
|
+
@property (nonatomic, assign) BOOL allowFontScaling;
|
|
45
|
+
@property (nonatomic, assign) CGFloat maxFontSizeMultiplier;
|
|
46
|
+
|
|
47
|
+
- (instancetype)init;
|
|
48
|
+
- (void)reset;
|
|
49
|
+
|
|
50
|
+
- (UIFont *)cachedFontForSize:(CGFloat)fontSize family:(NSString *)fontFamily weight:(NSString *)fontWeight;
|
|
51
|
+
- (NSMutableParagraphStyle *)spacerStyleWithHeight:(CGFloat)height spacing:(CGFloat)spacing;
|
|
52
|
+
- (NSMutableParagraphStyle *)blockSpacerStyleWithMargin:(CGFloat)margin;
|
|
53
|
+
- (void)registerLinkRange:(NSRange)range url:(NSString *)url;
|
|
54
|
+
- (void)registerHeadingRange:(NSRange)range level:(NSInteger)level text:(NSString *)text;
|
|
55
|
+
- (void)registerImageRange:(NSRange)range altText:(NSString *)altText url:(NSString *)url;
|
|
56
|
+
- (void)registerListItemRange:(NSRange)range
|
|
57
|
+
position:(NSInteger)position
|
|
58
|
+
depth:(NSInteger)depth
|
|
59
|
+
isOrdered:(BOOL)isOrdered;
|
|
60
|
+
- (void)setBlockStyle:(BlockType)type
|
|
61
|
+
fontSize:(CGFloat)fontSize
|
|
62
|
+
fontFamily:(NSString *)fontFamily
|
|
63
|
+
fontWeight:(NSString *)fontWeight
|
|
64
|
+
color:(UIColor *)color;
|
|
65
|
+
- (void)setBlockStyle:(BlockType)type
|
|
66
|
+
fontSize:(CGFloat)fontSize
|
|
67
|
+
fontFamily:(NSString *)fontFamily
|
|
68
|
+
fontWeight:(NSString *)fontWeight
|
|
69
|
+
color:(UIColor *)color
|
|
70
|
+
headingLevel:(NSInteger)headingLevel;
|
|
71
|
+
- (void)setBlockStyle:(BlockType)type font:(UIFont *)font color:(UIColor *)color headingLevel:(NSInteger)headingLevel;
|
|
72
|
+
- (BlockStyle *)getBlockStyle;
|
|
73
|
+
- (NSDictionary *)getTextAttributes;
|
|
74
|
+
- (void)clearBlockStyle;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Checks if colors should be preserved based on existing attributes.
|
|
78
|
+
* Returns YES if the text is inside a link or inline code, which means
|
|
79
|
+
* we should preserve their colors instead of applying new colors.
|
|
80
|
+
*/
|
|
81
|
+
+ (BOOL)shouldPreserveColors:(NSDictionary *)existingAttributes;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Calculates the color that strong would use based on the configured strong color and block style.
|
|
85
|
+
* Uses strongColor if explicitly set (different from block color), otherwise uses block color.
|
|
86
|
+
*/
|
|
87
|
+
+ (UIColor *)calculateStrongColor:(UIColor *)configStrongColor blockColor:(UIColor *)blockColor;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Calculates the range for content rendered between start and current output length.
|
|
91
|
+
* Returns a range with length 0 if no content was rendered.
|
|
92
|
+
*/
|
|
93
|
+
+ (NSRange)rangeForRenderedContent:(NSMutableAttributedString *)output start:(NSUInteger)start;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Applies font and color attributes conditionally, only updating if they've changed.
|
|
97
|
+
* Returns YES if any attributes were updated, NO otherwise.
|
|
98
|
+
*/
|
|
99
|
+
+ (BOOL)applyFontAndColorAttributes:(NSMutableAttributedString *)output
|
|
100
|
+
range:(NSRange)range
|
|
101
|
+
font:(UIFont *)font
|
|
102
|
+
color:(UIColor *)color
|
|
103
|
+
existingAttributes:(NSDictionary *)existingAttributes
|
|
104
|
+
shouldPreserveColors:(BOOL)shouldPreserveColors;
|
|
105
|
+
@end
|