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,35 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
|
|
3
|
+
@class RenderContext;
|
|
4
|
+
|
|
5
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Container for accessibility-related data extracted from RenderContext.
|
|
9
|
+
* Used by MarkdownAccessibilityElementBuilder to build VoiceOver elements.
|
|
10
|
+
*/
|
|
11
|
+
@interface AccessibilityInfo : NSObject
|
|
12
|
+
|
|
13
|
+
// Headings
|
|
14
|
+
@property (nonatomic, copy, readonly) NSArray<NSValue *> *headingRanges;
|
|
15
|
+
@property (nonatomic, copy, readonly) NSArray<NSNumber *> *headingLevels;
|
|
16
|
+
|
|
17
|
+
// Links
|
|
18
|
+
@property (nonatomic, copy, readonly) NSArray<NSValue *> *linkRanges;
|
|
19
|
+
@property (nonatomic, copy, readonly) NSArray<NSString *> *linkURLs;
|
|
20
|
+
|
|
21
|
+
// Images
|
|
22
|
+
@property (nonatomic, copy, readonly) NSArray<NSValue *> *imageRanges;
|
|
23
|
+
@property (nonatomic, copy, readonly) NSArray<NSString *> *imageAltTexts;
|
|
24
|
+
|
|
25
|
+
// List items
|
|
26
|
+
@property (nonatomic, copy, readonly) NSArray<NSValue *> *listItemRanges;
|
|
27
|
+
@property (nonatomic, copy, readonly) NSArray<NSNumber *> *listItemPositions;
|
|
28
|
+
@property (nonatomic, copy, readonly) NSArray<NSNumber *> *listItemDepths;
|
|
29
|
+
@property (nonatomic, copy, readonly) NSArray<NSNumber *> *listItemOrdered; // YES = ordered, NO = bullet
|
|
30
|
+
|
|
31
|
+
+ (instancetype)infoFromContext:(RenderContext *)context;
|
|
32
|
+
|
|
33
|
+
@end
|
|
34
|
+
|
|
35
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#import "AccessibilityInfo.h"
|
|
2
|
+
#import "RenderContext.h"
|
|
3
|
+
|
|
4
|
+
@implementation AccessibilityInfo
|
|
5
|
+
|
|
6
|
+
+ (instancetype)infoFromContext:(RenderContext *)context
|
|
7
|
+
{
|
|
8
|
+
AccessibilityInfo *info = [[AccessibilityInfo alloc] init];
|
|
9
|
+
if (info) {
|
|
10
|
+
info->_headingRanges = [context.headingRanges copy];
|
|
11
|
+
info->_headingLevels = [context.headingLevels copy];
|
|
12
|
+
info->_linkRanges = [context.linkRanges copy];
|
|
13
|
+
info->_linkURLs = [context.linkURLs copy];
|
|
14
|
+
info->_imageRanges = [context.imageRanges copy];
|
|
15
|
+
info->_imageAltTexts = [context.imageAltTexts copy];
|
|
16
|
+
info->_listItemRanges = [context.listItemRanges copy];
|
|
17
|
+
info->_listItemPositions = [context.listItemPositions copy];
|
|
18
|
+
info->_listItemDepths = [context.listItemDepths copy];
|
|
19
|
+
info->_listItemOrdered = [context.listItemOrdered copy];
|
|
20
|
+
}
|
|
21
|
+
return info;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#import "StyleConfig.h"
|
|
3
|
+
#import <UIKit/UIKit.h>
|
|
4
|
+
|
|
5
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
6
|
+
|
|
7
|
+
extern NSString *const BlockquoteDepthAttributeName;
|
|
8
|
+
extern NSString *const BlockquoteBackgroundColorAttributeName;
|
|
9
|
+
|
|
10
|
+
@interface BlockquoteBorder : NSObject
|
|
11
|
+
|
|
12
|
+
- (instancetype)initWithConfig:(StyleConfig *)config;
|
|
13
|
+
- (void)drawBordersForGlyphRange:(NSRange)glyphsToShow
|
|
14
|
+
layoutManager:(NSLayoutManager *)layoutManager
|
|
15
|
+
textContainer:(NSTextContainer *)textContainer
|
|
16
|
+
atPoint:(CGPoint)origin;
|
|
17
|
+
|
|
18
|
+
@end
|
|
19
|
+
|
|
20
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#import "BlockquoteBorder.h"
|
|
2
|
+
#import "StyleConfig.h"
|
|
3
|
+
|
|
4
|
+
// Attribute constants for identifying blockquote segments in text storage
|
|
5
|
+
NSString *const BlockquoteDepthAttributeName = @"BlockquoteDepth";
|
|
6
|
+
NSString *const BlockquoteBackgroundColorAttributeName = @"BlockquoteBackgroundColor";
|
|
7
|
+
|
|
8
|
+
@implementation BlockquoteBorder {
|
|
9
|
+
StyleConfig *_config;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
- (instancetype)initWithConfig:(StyleConfig *)config
|
|
13
|
+
{
|
|
14
|
+
if (self = [super init]) {
|
|
15
|
+
_config = config;
|
|
16
|
+
}
|
|
17
|
+
return self;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Main drawing entry point called by the LayoutManager.
|
|
22
|
+
* Iterates through line fragments to draw backgrounds and borders for nested blockquotes.
|
|
23
|
+
*/
|
|
24
|
+
- (void)drawBordersForGlyphRange:(NSRange)glyphsToShow
|
|
25
|
+
layoutManager:(NSLayoutManager *)layoutManager
|
|
26
|
+
textContainer:(NSTextContainer *)textContainer
|
|
27
|
+
atPoint:(CGPoint)origin
|
|
28
|
+
{
|
|
29
|
+
NSTextStorage *textStorage = layoutManager.textStorage;
|
|
30
|
+
if (!textStorage || textStorage.length == 0) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Cache configuration values to minimize pointer chasing and method lookups in the loop
|
|
35
|
+
StyleConfig *c = _config;
|
|
36
|
+
CGFloat borderWidth = c.blockquoteBorderWidth;
|
|
37
|
+
CGFloat gapWidth = c.blockquoteGapWidth;
|
|
38
|
+
CGFloat levelSpacing = borderWidth + gapWidth;
|
|
39
|
+
CGFloat containerWidth = textContainer.size.width;
|
|
40
|
+
UIColor *defaultBgColor = c.blockquoteBackgroundColor;
|
|
41
|
+
UIColor *borderColor = c.blockquoteBorderColor;
|
|
42
|
+
|
|
43
|
+
// Use a Bezier path to batch all vertical border rectangles into a single GPU draw call
|
|
44
|
+
UIBezierPath *borderPath = [UIBezierPath bezierPath];
|
|
45
|
+
|
|
46
|
+
[layoutManager
|
|
47
|
+
enumerateLineFragmentsForGlyphRange:glyphsToShow
|
|
48
|
+
usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer *container,
|
|
49
|
+
NSRange glyphRange, BOOL *stop) {
|
|
50
|
+
// Map the glyph range back to character indices to retrieve attributes
|
|
51
|
+
NSRange charRange = [layoutManager characterRangeForGlyphRange:glyphRange
|
|
52
|
+
actualGlyphRange:NULL];
|
|
53
|
+
if (charRange.location == NSNotFound || charRange.length == 0) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Perform a single attribute lookup for the current line fragment
|
|
58
|
+
NSDictionary *attrs = [textStorage attributesAtIndex:charRange.location
|
|
59
|
+
effectiveRange:NULL];
|
|
60
|
+
NSNumber *depthNum = attrs[BlockquoteDepthAttributeName];
|
|
61
|
+
|
|
62
|
+
// If no depth is found, this fragment is not part of a blockquote
|
|
63
|
+
if (!depthNum) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
NSInteger depth = [depthNum integerValue];
|
|
68
|
+
CGFloat baseY = origin.y + rect.origin.y;
|
|
69
|
+
|
|
70
|
+
// 1. Draw Background (Painter's algorithm: draw backgrounds before borders)
|
|
71
|
+
UIColor *bgColor = attrs[BlockquoteBackgroundColorAttributeName] ?: defaultBgColor;
|
|
72
|
+
if (bgColor && bgColor != [UIColor clearColor]) {
|
|
73
|
+
[bgColor setFill];
|
|
74
|
+
UIRectFill(CGRectMake(origin.x, baseY, containerWidth, rect.size.height));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 2. Aggregate vertical borders into the batch path
|
|
78
|
+
for (NSInteger level = 0; level <= depth; level++) {
|
|
79
|
+
CGFloat borderX = origin.x + (levelSpacing * level);
|
|
80
|
+
CGRect borderRect = CGRectMake(borderX, baseY, borderWidth, rect.size.height);
|
|
81
|
+
[borderPath appendPath:[UIBezierPath bezierPathWithRect:borderRect]];
|
|
82
|
+
}
|
|
83
|
+
}];
|
|
84
|
+
|
|
85
|
+
// 3. Perform a single batch fill for all borders
|
|
86
|
+
if (!borderPath.isEmpty) {
|
|
87
|
+
[borderColor setFill];
|
|
88
|
+
[borderPath fill];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#import "StyleConfig.h"
|
|
3
|
+
#import <UIKit/UIKit.h>
|
|
4
|
+
|
|
5
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
6
|
+
|
|
7
|
+
extern NSString *const CodeAttributeName;
|
|
8
|
+
|
|
9
|
+
@interface CodeBackground : NSObject
|
|
10
|
+
|
|
11
|
+
- (instancetype)initWithConfig:(StyleConfig *)config;
|
|
12
|
+
- (void)drawBackgroundsForGlyphRange:(NSRange)glyphsToShow
|
|
13
|
+
layoutManager:(NSLayoutManager *)layoutManager
|
|
14
|
+
textContainer:(NSTextContainer *)textContainer
|
|
15
|
+
atPoint:(CGPoint)origin;
|
|
16
|
+
|
|
17
|
+
@end
|
|
18
|
+
|
|
19
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#import "CodeBackground.h"
|
|
2
|
+
#import "RenderContext.h"
|
|
3
|
+
|
|
4
|
+
NSString *const CodeAttributeName = @"Code";
|
|
5
|
+
|
|
6
|
+
static const CGFloat kCodeBackgroundCornerRadius = 2.0;
|
|
7
|
+
static const CGFloat kCodeBackgroundBorderWidth = 0.5;
|
|
8
|
+
|
|
9
|
+
@implementation CodeBackground {
|
|
10
|
+
StyleConfig *_config;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
- (instancetype)initWithConfig:(StyleConfig *)config
|
|
14
|
+
{
|
|
15
|
+
self = [super init];
|
|
16
|
+
if (self) {
|
|
17
|
+
_config = config;
|
|
18
|
+
}
|
|
19
|
+
return self;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
- (void)drawBackgroundsForGlyphRange:(NSRange)glyphsToShow
|
|
23
|
+
layoutManager:(NSLayoutManager *)layoutManager
|
|
24
|
+
textContainer:(NSTextContainer *)textContainer
|
|
25
|
+
atPoint:(CGPoint)origin
|
|
26
|
+
{
|
|
27
|
+
UIColor *backgroundColor = _config.codeBackgroundColor;
|
|
28
|
+
if (!backgroundColor)
|
|
29
|
+
return;
|
|
30
|
+
|
|
31
|
+
NSTextStorage *textStorage = layoutManager.textStorage;
|
|
32
|
+
NSRange charRange = [layoutManager characterRangeForGlyphRange:glyphsToShow actualGlyphRange:NULL];
|
|
33
|
+
if (charRange.location == NSNotFound || charRange.length == 0)
|
|
34
|
+
return;
|
|
35
|
+
|
|
36
|
+
[textStorage enumerateAttribute:CodeAttributeName
|
|
37
|
+
inRange:NSMakeRange(0, textStorage.length)
|
|
38
|
+
options:0
|
|
39
|
+
usingBlock:^(id value, NSRange range, BOOL *stop) {
|
|
40
|
+
if (!value || range.length == 0)
|
|
41
|
+
return;
|
|
42
|
+
if (NSIntersectionRange(range, charRange).length == 0)
|
|
43
|
+
return;
|
|
44
|
+
|
|
45
|
+
[self drawCodeBackgroundForRange:range
|
|
46
|
+
layoutManager:layoutManager
|
|
47
|
+
textContainer:textContainer
|
|
48
|
+
atPoint:origin
|
|
49
|
+
backgroundColor:backgroundColor
|
|
50
|
+
borderColor:self->_config.codeBorderColor];
|
|
51
|
+
}];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
- (void)drawCodeBackgroundForRange:(NSRange)range
|
|
55
|
+
layoutManager:(NSLayoutManager *)layoutManager
|
|
56
|
+
textContainer:(NSTextContainer *)textContainer
|
|
57
|
+
atPoint:(CGPoint)origin
|
|
58
|
+
backgroundColor:(UIColor *)backgroundColor
|
|
59
|
+
borderColor:(UIColor *)borderColor
|
|
60
|
+
{
|
|
61
|
+
NSRange glyphRange = [layoutManager glyphRangeForCharacterRange:range actualCharacterRange:NULL];
|
|
62
|
+
if (glyphRange.location == NSNotFound || glyphRange.length == 0)
|
|
63
|
+
return;
|
|
64
|
+
|
|
65
|
+
CGFloat referenceHeight = [self findReferenceHeightForRange:range textStorage:layoutManager.textStorage];
|
|
66
|
+
|
|
67
|
+
[layoutManager
|
|
68
|
+
enumerateLineFragmentsForGlyphRange:glyphRange
|
|
69
|
+
usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer *tc, NSRange lineRange,
|
|
70
|
+
BOOL *stop) {
|
|
71
|
+
NSRange intersect = NSIntersectionRange(lineRange, glyphRange);
|
|
72
|
+
if (intersect.length == 0)
|
|
73
|
+
return;
|
|
74
|
+
|
|
75
|
+
BOOL isFirst = (intersect.location == glyphRange.location);
|
|
76
|
+
BOOL isLast = (NSMaxRange(intersect) == NSMaxRange(glyphRange));
|
|
77
|
+
|
|
78
|
+
CGRect finalRect;
|
|
79
|
+
if (isFirst || isLast) {
|
|
80
|
+
// Precise bounds are only required for the start and end of the span
|
|
81
|
+
CGRect textRect = [layoutManager boundingRectForGlyphRange:intersect
|
|
82
|
+
inTextContainer:textContainer];
|
|
83
|
+
finalRect = CGRectMake(textRect.origin.x + origin.x, textRect.origin.y + origin.y,
|
|
84
|
+
textRect.size.width, textRect.size.height);
|
|
85
|
+
|
|
86
|
+
// For multi-line, extend the first line to the right edge of the fragment
|
|
87
|
+
if (isFirst && !isLast) {
|
|
88
|
+
finalRect.size.width =
|
|
89
|
+
(usedRect.origin.x + usedRect.size.width + origin.x) - finalRect.origin.x;
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
// OPTIMIZATION: Middle lines use the usedRect of the fragment directly
|
|
93
|
+
finalRect = CGRectMake(usedRect.origin.x + origin.x, usedRect.origin.y + origin.y,
|
|
94
|
+
usedRect.size.width, usedRect.size.height);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Ensure consistent height and no gaps
|
|
98
|
+
if (finalRect.size.height < referenceHeight) {
|
|
99
|
+
finalRect.size.height = referenceHeight;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
[self drawBackgroundAndBorders:finalRect
|
|
103
|
+
backgroundColor:backgroundColor
|
|
104
|
+
borderColor:borderColor
|
|
105
|
+
isFirst:isFirst
|
|
106
|
+
isLast:isLast];
|
|
107
|
+
}];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
#pragma mark - Drawing Logic
|
|
111
|
+
|
|
112
|
+
- (void)drawBackgroundAndBorders:(CGRect)rect
|
|
113
|
+
backgroundColor:(UIColor *)backgroundColor
|
|
114
|
+
borderColor:(UIColor *)borderColor
|
|
115
|
+
isFirst:(BOOL)isFirst
|
|
116
|
+
isLast:(BOOL)isLast
|
|
117
|
+
{
|
|
118
|
+
UIBezierPath *path = [self pathForRect:rect isFirst:isFirst isLast:isLast];
|
|
119
|
+
[backgroundColor setFill];
|
|
120
|
+
[path fill];
|
|
121
|
+
|
|
122
|
+
if (borderColor) {
|
|
123
|
+
[borderColor setStroke];
|
|
124
|
+
UIBezierPath *strokePath =
|
|
125
|
+
(isFirst && isLast) ? path : [self openBorderPathForRect:rect isFirst:isFirst isLast:isLast];
|
|
126
|
+
strokePath.lineWidth = kCodeBackgroundBorderWidth;
|
|
127
|
+
strokePath.lineCapStyle = kCGLineCapRound;
|
|
128
|
+
strokePath.lineJoinStyle = kCGLineJoinRound;
|
|
129
|
+
[strokePath stroke];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
- (UIBezierPath *)pathForRect:(CGRect)rect isFirst:(BOOL)isFirst isLast:(BOOL)isLast
|
|
134
|
+
{
|
|
135
|
+
UIRectCorner corners = 0;
|
|
136
|
+
if (isFirst)
|
|
137
|
+
corners |= (UIRectCornerTopLeft | UIRectCornerBottomLeft);
|
|
138
|
+
if (isLast)
|
|
139
|
+
corners |= (UIRectCornerTopRight | UIRectCornerBottomRight);
|
|
140
|
+
|
|
141
|
+
return [UIBezierPath bezierPathWithRoundedRect:rect
|
|
142
|
+
byRoundingCorners:corners
|
|
143
|
+
cornerRadii:CGSizeMake(kCodeBackgroundCornerRadius, kCodeBackgroundCornerRadius)];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
- (UIBezierPath *)openBorderPathForRect:(CGRect)rect isFirst:(BOOL)isFirst isLast:(BOOL)isLast
|
|
147
|
+
{
|
|
148
|
+
UIBezierPath *path = [UIBezierPath bezierPath];
|
|
149
|
+
CGFloat r = kCodeBackgroundCornerRadius;
|
|
150
|
+
CGFloat inset = kCodeBackgroundBorderWidth / 2.0;
|
|
151
|
+
CGRect insetRect = CGRectInset(rect, inset, inset);
|
|
152
|
+
|
|
153
|
+
if (isFirst) {
|
|
154
|
+
[path moveToPoint:CGPointMake(CGRectGetMaxX(rect), insetRect.origin.y)];
|
|
155
|
+
[path addLineToPoint:CGPointMake(insetRect.origin.x + r, insetRect.origin.y)];
|
|
156
|
+
[path addQuadCurveToPoint:CGPointMake(insetRect.origin.x, insetRect.origin.y + r)
|
|
157
|
+
controlPoint:CGPointMake(insetRect.origin.x, insetRect.origin.y)];
|
|
158
|
+
[path addLineToPoint:CGPointMake(insetRect.origin.x, CGRectGetMaxY(insetRect) - r)];
|
|
159
|
+
[path addQuadCurveToPoint:CGPointMake(insetRect.origin.x + r, CGRectGetMaxY(insetRect))
|
|
160
|
+
controlPoint:CGPointMake(insetRect.origin.x, CGRectGetMaxY(insetRect))];
|
|
161
|
+
[path addLineToPoint:CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(insetRect))];
|
|
162
|
+
} else if (isLast) {
|
|
163
|
+
[path moveToPoint:CGPointMake(rect.origin.x, insetRect.origin.y)];
|
|
164
|
+
[path addLineToPoint:CGPointMake(CGRectGetMaxX(insetRect) - r, insetRect.origin.y)];
|
|
165
|
+
[path addQuadCurveToPoint:CGPointMake(CGRectGetMaxX(insetRect), insetRect.origin.y + r)
|
|
166
|
+
controlPoint:CGPointMake(CGRectGetMaxX(insetRect), insetRect.origin.y)];
|
|
167
|
+
[path addLineToPoint:CGPointMake(CGRectGetMaxX(insetRect), CGRectGetMaxY(insetRect) - r)];
|
|
168
|
+
[path addQuadCurveToPoint:CGPointMake(CGRectGetMaxX(insetRect) - r, CGRectGetMaxY(insetRect))
|
|
169
|
+
controlPoint:CGPointMake(CGRectGetMaxX(insetRect), CGRectGetMaxY(insetRect))];
|
|
170
|
+
[path addLineToPoint:CGPointMake(rect.origin.x, CGRectGetMaxY(insetRect))];
|
|
171
|
+
} else {
|
|
172
|
+
[path moveToPoint:CGPointMake(rect.origin.x, insetRect.origin.y)];
|
|
173
|
+
[path addLineToPoint:CGPointMake(CGRectGetMaxX(rect), insetRect.origin.y)];
|
|
174
|
+
[path moveToPoint:CGPointMake(rect.origin.x, CGRectGetMaxY(insetRect))];
|
|
175
|
+
[path addLineToPoint:CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(insetRect))];
|
|
176
|
+
}
|
|
177
|
+
return path;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#pragma mark - Helpers
|
|
181
|
+
|
|
182
|
+
- (CGFloat)findReferenceHeightForRange:(NSRange)range textStorage:(NSTextStorage *)textStorage
|
|
183
|
+
{
|
|
184
|
+
if (range.location == NSNotFound || range.length == 0 || !textStorage) {
|
|
185
|
+
return [_config paragraphFontSize] * 1.2;
|
|
186
|
+
}
|
|
187
|
+
NSNumber *lineHeightValue = [textStorage attribute:@"BlockLineHeight" atIndex:range.location effectiveRange:NULL];
|
|
188
|
+
return lineHeightValue ? [lineHeightValue doubleValue] : ([_config paragraphFontSize] * 1.2);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#import "StyleConfig.h"
|
|
3
|
+
#import <UIKit/UIKit.h>
|
|
4
|
+
|
|
5
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
6
|
+
|
|
7
|
+
@interface CodeBlockBackground : NSObject
|
|
8
|
+
|
|
9
|
+
- (instancetype)initWithConfig:(StyleConfig *)config;
|
|
10
|
+
- (void)drawBackgroundsForGlyphRange:(NSRange)glyphsToShow
|
|
11
|
+
layoutManager:(NSLayoutManager *)layoutManager
|
|
12
|
+
textContainer:(NSTextContainer *)textContainer
|
|
13
|
+
atPoint:(CGPoint)origin;
|
|
14
|
+
|
|
15
|
+
@end
|
|
16
|
+
|
|
17
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#import "CodeBlockBackground.h"
|
|
2
|
+
#import "LastElementUtils.h"
|
|
3
|
+
#import "StyleConfig.h"
|
|
4
|
+
|
|
5
|
+
@implementation CodeBlockBackground {
|
|
6
|
+
StyleConfig *_config;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
- (instancetype)initWithConfig:(StyleConfig *)config
|
|
10
|
+
{
|
|
11
|
+
if (self = [super init]) {
|
|
12
|
+
_config = config;
|
|
13
|
+
}
|
|
14
|
+
return self;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
- (void)drawBackgroundsForGlyphRange:(NSRange)glyphsToShow
|
|
18
|
+
layoutManager:(NSLayoutManager *)layoutManager
|
|
19
|
+
textContainer:(NSTextContainer *)textContainer
|
|
20
|
+
atPoint:(CGPoint)origin
|
|
21
|
+
{
|
|
22
|
+
NSTextStorage *textStorage = layoutManager.textStorage;
|
|
23
|
+
NSRange charRange = [layoutManager characterRangeForGlyphRange:glyphsToShow actualGlyphRange:NULL];
|
|
24
|
+
|
|
25
|
+
[textStorage enumerateAttribute:CodeBlockAttributeName
|
|
26
|
+
inRange:charRange
|
|
27
|
+
options:0
|
|
28
|
+
usingBlock:^(id value, NSRange range, BOOL *stop) {
|
|
29
|
+
if (!value)
|
|
30
|
+
return;
|
|
31
|
+
[self drawCodeBlockBackgroundForRange:range
|
|
32
|
+
layoutManager:layoutManager
|
|
33
|
+
textContainer:textContainer
|
|
34
|
+
atPoint:origin];
|
|
35
|
+
}];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
- (void)drawCodeBlockBackgroundForRange:(NSRange)range
|
|
39
|
+
layoutManager:(NSLayoutManager *)layoutManager
|
|
40
|
+
textContainer:(NSTextContainer *)textContainer
|
|
41
|
+
atPoint:(CGPoint)origin
|
|
42
|
+
{
|
|
43
|
+
NSRange glyphRange = [layoutManager glyphRangeForCharacterRange:range actualCharacterRange:NULL];
|
|
44
|
+
CGRect blockRect = [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer];
|
|
45
|
+
|
|
46
|
+
if (CGRectIsEmpty(blockRect))
|
|
47
|
+
return;
|
|
48
|
+
|
|
49
|
+
blockRect.origin.x = origin.x;
|
|
50
|
+
blockRect.origin.y += origin.y;
|
|
51
|
+
blockRect.size.width = textContainer.size.width;
|
|
52
|
+
|
|
53
|
+
// We extend the background specifically to cover the bottom padding spacer,
|
|
54
|
+
// excluding any additional marginBottom applied via measurement.
|
|
55
|
+
BOOL isLastCodeBlock = (NSMaxRange(range) == layoutManager.textStorage.length);
|
|
56
|
+
if (isLastCodeBlock) {
|
|
57
|
+
blockRect.size.height += [_config codeBlockPadding];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
CGFloat borderWidth = [_config codeBlockBorderWidth];
|
|
61
|
+
CGFloat borderRadius = [_config codeBlockBorderRadius];
|
|
62
|
+
CGFloat inset = borderWidth / 2.0;
|
|
63
|
+
|
|
64
|
+
CGRect insetRect = CGRectInset(blockRect, inset, inset);
|
|
65
|
+
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:insetRect cornerRadius:MAX(0, borderRadius - inset)];
|
|
66
|
+
|
|
67
|
+
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
|
68
|
+
CGContextSaveGState(ctx);
|
|
69
|
+
{
|
|
70
|
+
[[_config codeBlockBackgroundColor] setFill];
|
|
71
|
+
[path fill];
|
|
72
|
+
|
|
73
|
+
if (borderWidth > 0) {
|
|
74
|
+
[[_config codeBlockBorderColor] setStroke];
|
|
75
|
+
path.lineWidth = borderWidth;
|
|
76
|
+
path.lineJoinStyle = kCGLineJoinRound;
|
|
77
|
+
[path stroke];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
CGContextRestoreGState(ctx);
|
|
81
|
+
}
|
|
82
|
+
@end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#import <Foundation/Foundation.h>
|
|
3
|
+
#import <UIKit/UIKit.h>
|
|
4
|
+
|
|
5
|
+
@class StyleConfig;
|
|
6
|
+
|
|
7
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
8
|
+
|
|
9
|
+
#ifdef __cplusplus
|
|
10
|
+
extern "C" {
|
|
11
|
+
#endif
|
|
12
|
+
|
|
13
|
+
/// Builds edit menu with enhanced Copy (RTF/HTML/Markdown) and optional "Copy as Markdown"/"Copy Image URL".
|
|
14
|
+
UIMenu *buildEditMenuForSelection(NSAttributedString *attributedText, NSRange range, NSString *_Nullable cachedMarkdown,
|
|
15
|
+
StyleConfig *styleConfig, NSArray<UIMenuElement *> *suggestedActions)
|
|
16
|
+
API_AVAILABLE(ios(16.0));
|
|
17
|
+
|
|
18
|
+
#ifdef __cplusplus
|
|
19
|
+
}
|
|
20
|
+
#endif
|
|
21
|
+
|
|
22
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#import "EditMenuUtils.h"
|
|
2
|
+
#import "PasteboardUtils.h"
|
|
3
|
+
#import "StyleConfig.h"
|
|
4
|
+
#import <UIKit/UIPasteboard.h>
|
|
5
|
+
|
|
6
|
+
static NSString *const kMenuIdentifierStandardEdit = @"com.apple.menu.standard-edit";
|
|
7
|
+
static NSString *const kActionIdentifierCopy = @"com.swmansion.enriched.markdown.copy";
|
|
8
|
+
static NSString *const kActionIdentifierCopyMarkdown = @"com.swmansion.enriched.markdown.copyMarkdown";
|
|
9
|
+
static NSString *const kActionIdentifierCopyImageURL = @"com.swmansion.enriched.markdown.copyImageURL";
|
|
10
|
+
|
|
11
|
+
#pragma mark - Action Creators
|
|
12
|
+
|
|
13
|
+
static UIAction *createCopyAction(NSAttributedString *selectedText, NSString *markdown, StyleConfig *styleConfig)
|
|
14
|
+
{
|
|
15
|
+
return [UIAction actionWithTitle:@"Copy"
|
|
16
|
+
image:[UIImage systemImageNamed:@"doc.on.doc"]
|
|
17
|
+
identifier:kActionIdentifierCopy
|
|
18
|
+
handler:^(__kindof UIAction *action) {
|
|
19
|
+
copyAttributedStringToPasteboard(selectedText, markdown, styleConfig);
|
|
20
|
+
}];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static UIAction *_Nullable createCopyMarkdownAction(NSString *markdown)
|
|
24
|
+
{
|
|
25
|
+
if (markdown.length == 0)
|
|
26
|
+
return nil;
|
|
27
|
+
|
|
28
|
+
return [UIAction
|
|
29
|
+
actionWithTitle:@"Copy as Markdown"
|
|
30
|
+
image:[UIImage systemImageNamed:@"doc.text"]
|
|
31
|
+
identifier:kActionIdentifierCopyMarkdown
|
|
32
|
+
handler:^(__kindof UIAction *action) { [[UIPasteboard generalPasteboard] setString:markdown]; }];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static UIAction *_Nullable createCopyImageURLAction(NSArray<NSString *> *imageURLs)
|
|
36
|
+
{
|
|
37
|
+
if (imageURLs.count == 0)
|
|
38
|
+
return nil;
|
|
39
|
+
|
|
40
|
+
NSString *urlsToCopy = [imageURLs componentsJoinedByString:@"\n"];
|
|
41
|
+
NSString *title = (imageURLs.count == 1)
|
|
42
|
+
? @"Copy Image URL"
|
|
43
|
+
: [NSString stringWithFormat:@"Copy %lu Image URLs", (unsigned long)imageURLs.count];
|
|
44
|
+
|
|
45
|
+
return [UIAction
|
|
46
|
+
actionWithTitle:title
|
|
47
|
+
image:[UIImage systemImageNamed:@"link"]
|
|
48
|
+
identifier:kActionIdentifierCopyImageURL
|
|
49
|
+
handler:^(__kindof UIAction *action) { [[UIPasteboard generalPasteboard] setString:urlsToCopy]; }];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#pragma mark - Menu Building Helpers
|
|
53
|
+
|
|
54
|
+
static UIMenu *createEnhancedStandardEditMenu(UIMenu *originalMenu, UIAction *copyAction)
|
|
55
|
+
{
|
|
56
|
+
return [UIMenu menuWithTitle:originalMenu.title
|
|
57
|
+
image:originalMenu.image
|
|
58
|
+
identifier:originalMenu.identifier
|
|
59
|
+
options:originalMenu.options
|
|
60
|
+
children:@[ copyAction ]];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static void addOptionalAction(NSMutableArray<UIMenuElement *> *array, UIAction *_Nullable action)
|
|
64
|
+
{
|
|
65
|
+
if (action) {
|
|
66
|
+
[array addObject:action];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static void insertOptionalAction(NSMutableArray<UIMenuElement *> *array, UIAction *_Nullable action, NSUInteger index)
|
|
71
|
+
{
|
|
72
|
+
if (action) {
|
|
73
|
+
[array insertObject:action atIndex:index];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#pragma mark - Public API
|
|
78
|
+
|
|
79
|
+
UIMenu *buildEditMenuForSelection(NSAttributedString *attributedText, NSRange range, NSString *_Nullable cachedMarkdown,
|
|
80
|
+
StyleConfig *styleConfig, NSArray<UIMenuElement *> *suggestedActions)
|
|
81
|
+
API_AVAILABLE(ios(16.0))
|
|
82
|
+
{
|
|
83
|
+
NSAttributedString *selectedText = [attributedText attributedSubstringFromRange:range];
|
|
84
|
+
NSString *markdown = markdownForRange(attributedText, range, cachedMarkdown);
|
|
85
|
+
NSArray<NSString *> *imageURLs = imageURLsInRange(attributedText, range);
|
|
86
|
+
|
|
87
|
+
UIAction *copyAction = createCopyAction(selectedText, markdown, styleConfig);
|
|
88
|
+
UIAction *copyMarkdownAction = createCopyMarkdownAction(markdown);
|
|
89
|
+
UIAction *copyImageURLAction = createCopyImageURLAction(imageURLs);
|
|
90
|
+
|
|
91
|
+
NSMutableArray<UIMenuElement *> *result = [NSMutableArray array];
|
|
92
|
+
BOOL foundStandardEditMenu = NO;
|
|
93
|
+
|
|
94
|
+
for (UIMenuElement *element in suggestedActions) {
|
|
95
|
+
if ([element isKindOfClass:[UIMenu class]]) {
|
|
96
|
+
UIMenu *menu = (UIMenu *)element;
|
|
97
|
+
|
|
98
|
+
if ([menu.identifier isEqualToString:kMenuIdentifierStandardEdit]) {
|
|
99
|
+
// Replace standard Copy with our enhanced version
|
|
100
|
+
[result addObject:createEnhancedStandardEditMenu(menu, copyAction)];
|
|
101
|
+
addOptionalAction(result, copyMarkdownAction);
|
|
102
|
+
addOptionalAction(result, copyImageURLAction);
|
|
103
|
+
foundStandardEditMenu = YES;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
[result addObject:element];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Fallback if standard-edit menu wasn't found
|
|
111
|
+
if (!foundStandardEditMenu) {
|
|
112
|
+
[result insertObject:copyAction atIndex:0];
|
|
113
|
+
insertOptionalAction(result, copyMarkdownAction, 1);
|
|
114
|
+
addOptionalAction(result, copyImageURLAction);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return [UIMenu menuWithChildren:result];
|
|
118
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
#import <UIKit/UIKit.h>
|
|
3
|
+
|
|
4
|
+
@class BlockStyle;
|
|
5
|
+
@class RenderContext;
|
|
6
|
+
|
|
7
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
8
|
+
|
|
9
|
+
#ifdef __cplusplus
|
|
10
|
+
extern "C" {
|
|
11
|
+
#endif
|
|
12
|
+
|
|
13
|
+
/** Returns a cached UIFont from BlockStyle properties via RenderContext. */
|
|
14
|
+
extern UIFont *cachedFontFromBlockStyle(BlockStyle *blockStyle, RenderContext *context);
|
|
15
|
+
|
|
16
|
+
/** Returns the font scale multiplier, capped by maxFontSizeMultiplier.
|
|
17
|
+
* Uses React Native's RCTFontSizeMultiplier() internally.
|
|
18
|
+
* @param maxFontSizeMultiplier Values >= 1.0 cap the result, < 1.0 means no cap. */
|
|
19
|
+
extern CGFloat RCTFontSizeMultiplierWithMax(CGFloat maxFontSizeMultiplier);
|
|
20
|
+
|
|
21
|
+
#ifdef __cplusplus
|
|
22
|
+
}
|
|
23
|
+
#endif
|
|
24
|
+
|
|
25
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#import "FontUtils.h"
|
|
2
|
+
#import "RenderContext.h"
|
|
3
|
+
#import <React/RCTUtils.h>
|
|
4
|
+
|
|
5
|
+
UIFont *cachedFontFromBlockStyle(BlockStyle *blockStyle, RenderContext *context)
|
|
6
|
+
{
|
|
7
|
+
if (!blockStyle) {
|
|
8
|
+
return nil;
|
|
9
|
+
}
|
|
10
|
+
if (blockStyle.cachedFont) {
|
|
11
|
+
return blockStyle.cachedFont;
|
|
12
|
+
}
|
|
13
|
+
return [context cachedFontForSize:blockStyle.fontSize family:blockStyle.fontFamily weight:blockStyle.fontWeight];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
CGFloat RCTFontSizeMultiplierWithMax(CGFloat maxFontSizeMultiplier)
|
|
17
|
+
{
|
|
18
|
+
CGFloat multiplier = RCTFontSizeMultiplier();
|
|
19
|
+
|
|
20
|
+
// Apply maxFontSizeMultiplier cap if >= 1.0
|
|
21
|
+
// Values < 1.0 (including 0 and NaN) mean no cap is applied
|
|
22
|
+
if (!isnan(maxFontSizeMultiplier) && maxFontSizeMultiplier >= 1.0) {
|
|
23
|
+
return fmin(maxFontSizeMultiplier, multiplier);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return multiplier;
|
|
27
|
+
}
|