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,53 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#import <UIKit/UIKit.h>
|
|
3
|
+
|
|
4
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
5
|
+
|
|
6
|
+
static NSString *const CodeBlockAttributeName = @"CodeBlock";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Checks if the last element in the attributed string is a code block.
|
|
10
|
+
* Used to compensate for iOS text APIs not measuring/drawing trailing newlines with custom line heights.
|
|
11
|
+
*/
|
|
12
|
+
static inline BOOL isLastElementCodeBlock(NSAttributedString *text)
|
|
13
|
+
{
|
|
14
|
+
if (text.length == 0)
|
|
15
|
+
return NO;
|
|
16
|
+
|
|
17
|
+
NSRange lastContent = [text.string rangeOfCharacterFromSet:[[NSCharacterSet newlineCharacterSet] invertedSet]
|
|
18
|
+
options:NSBackwardsSearch];
|
|
19
|
+
if (lastContent.location == NSNotFound)
|
|
20
|
+
return NO;
|
|
21
|
+
|
|
22
|
+
NSNumber *isCodeBlock = [text attribute:CodeBlockAttributeName atIndex:lastContent.location effectiveRange:nil];
|
|
23
|
+
if (!isCodeBlock.boolValue)
|
|
24
|
+
return NO;
|
|
25
|
+
|
|
26
|
+
NSRange codeBlockRange;
|
|
27
|
+
[text attribute:CodeBlockAttributeName atIndex:lastContent.location effectiveRange:&codeBlockRange];
|
|
28
|
+
return NSMaxRange(codeBlockRange) == text.length;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Checks if the last element in the attributed string is an image attachment.
|
|
33
|
+
* Used to compensate for iOS text attachment baseline spacing issues.
|
|
34
|
+
*/
|
|
35
|
+
static inline BOOL isLastElementImage(NSAttributedString *text)
|
|
36
|
+
{
|
|
37
|
+
if (text.length == 0)
|
|
38
|
+
return NO;
|
|
39
|
+
|
|
40
|
+
NSRange lastContent = [text.string rangeOfCharacterFromSet:[[NSCharacterSet newlineCharacterSet] invertedSet]
|
|
41
|
+
options:NSBackwardsSearch];
|
|
42
|
+
if (lastContent.location == NSNotFound)
|
|
43
|
+
return NO;
|
|
44
|
+
|
|
45
|
+
unichar lastChar = [text.string characterAtIndex:lastContent.location];
|
|
46
|
+
if (lastChar != 0xFFFC)
|
|
47
|
+
return NO;
|
|
48
|
+
|
|
49
|
+
id attachment = [text attribute:NSAttachmentAttributeName atIndex:lastContent.location effectiveRange:nil];
|
|
50
|
+
return attachment != nil;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
#import <UIKit/UIKit.h>
|
|
3
|
+
|
|
4
|
+
@class StyleConfig;
|
|
5
|
+
|
|
6
|
+
@interface ListMarkerDrawer : NSObject
|
|
7
|
+
|
|
8
|
+
- (instancetype)initWithConfig:(StyleConfig *)config;
|
|
9
|
+
|
|
10
|
+
- (void)drawMarkersForGlyphRange:(NSRange)glyphsToShow
|
|
11
|
+
layoutManager:(NSLayoutManager *)layoutManager
|
|
12
|
+
textContainer:(NSTextContainer *)textContainer
|
|
13
|
+
atPoint:(CGPoint)origin;
|
|
14
|
+
|
|
15
|
+
@end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#import "ListMarkerDrawer.h"
|
|
2
|
+
#import "ListItemRenderer.h"
|
|
3
|
+
#import "RenderContext.h"
|
|
4
|
+
#import "StyleConfig.h"
|
|
5
|
+
|
|
6
|
+
// Reference external symbols defined in ListItemRenderer.m
|
|
7
|
+
extern NSString *const ListDepthAttribute;
|
|
8
|
+
extern NSString *const ListTypeAttribute;
|
|
9
|
+
extern NSString *const ListItemNumberAttribute;
|
|
10
|
+
|
|
11
|
+
@implementation ListMarkerDrawer {
|
|
12
|
+
StyleConfig *_config;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
- (instancetype)initWithConfig:(StyleConfig *)config
|
|
16
|
+
{
|
|
17
|
+
if (self = [super init]) {
|
|
18
|
+
_config = config;
|
|
19
|
+
}
|
|
20
|
+
return self;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
- (void)drawMarkersForGlyphRange:(NSRange)glyphsToShow
|
|
24
|
+
layoutManager:(NSLayoutManager *)layoutManager
|
|
25
|
+
textContainer:(NSTextContainer *)textContainer
|
|
26
|
+
atPoint:(CGPoint)origin
|
|
27
|
+
{
|
|
28
|
+
NSTextStorage *storage = layoutManager.textStorage;
|
|
29
|
+
if (!storage || storage.length == 0)
|
|
30
|
+
return;
|
|
31
|
+
|
|
32
|
+
// Cache gap and track paragraphs to prevent double-drawing on wrapped lines
|
|
33
|
+
CGFloat gap = [_config effectiveListGapWidth];
|
|
34
|
+
NSMutableSet *drawnParagraphs = [NSMutableSet set];
|
|
35
|
+
|
|
36
|
+
[layoutManager
|
|
37
|
+
enumerateLineFragmentsForGlyphRange:glyphsToShow
|
|
38
|
+
usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer *container,
|
|
39
|
+
NSRange glyphRange, BOOL *stop) {
|
|
40
|
+
NSRange charRange = [layoutManager characterRangeForGlyphRange:glyphRange
|
|
41
|
+
actualGlyphRange:NULL];
|
|
42
|
+
if (charRange.location == NSNotFound)
|
|
43
|
+
return;
|
|
44
|
+
|
|
45
|
+
// 1. Fetch all attributes at once for efficiency
|
|
46
|
+
NSDictionary *attrs = [storage attributesAtIndex:charRange.location
|
|
47
|
+
effectiveRange:NULL];
|
|
48
|
+
if (!attrs[ListDepthAttribute])
|
|
49
|
+
return;
|
|
50
|
+
|
|
51
|
+
// 2. Identify the start of the paragraph
|
|
52
|
+
NSRange paraRange = [storage.string paragraphRangeForRange:charRange];
|
|
53
|
+
if (charRange.location != paraRange.location ||
|
|
54
|
+
[drawnParagraphs containsObject:@(paraRange.location)])
|
|
55
|
+
return;
|
|
56
|
+
[drawnParagraphs addObject:@(paraRange.location)];
|
|
57
|
+
|
|
58
|
+
// 3. Calculate Layout Coordinates
|
|
59
|
+
CGPoint glyphLoc = [layoutManager locationForGlyphAtIndex:glyphRange.location];
|
|
60
|
+
CGFloat baselineY = origin.y + rect.origin.y + glyphLoc.y;
|
|
61
|
+
CGFloat textStartX = origin.x + usedRect.origin.x;
|
|
62
|
+
|
|
63
|
+
// 4. Draw marker based on type
|
|
64
|
+
if ([attrs[ListTypeAttribute] integerValue] == ListTypeUnordered) {
|
|
65
|
+
UIFont *font = attrs[NSFontAttributeName] ?: [self defaultFont];
|
|
66
|
+
[self drawBulletAtX:textStartX - gap centerY:baselineY - (font.xHeight / 2.0)];
|
|
67
|
+
} else {
|
|
68
|
+
[self drawOrderedMarkerAtX:textStartX - gap attrs:attrs baselineY:baselineY];
|
|
69
|
+
}
|
|
70
|
+
}];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
#pragma mark - Drawing Helpers
|
|
74
|
+
|
|
75
|
+
- (void)drawBulletAtX:(CGFloat)x centerY:(CGFloat)y
|
|
76
|
+
{
|
|
77
|
+
[self
|
|
78
|
+
executeDrawing:^(CGContextRef ctx) {
|
|
79
|
+
[[_config listStyleBulletColor] ?: [UIColor blackColor] setFill];
|
|
80
|
+
CGFloat size = [_config listStyleBulletSize];
|
|
81
|
+
CGContextFillEllipseInRect(ctx, CGRectMake(x - size / 2.0, y - size / 2.0, size, size));
|
|
82
|
+
}
|
|
83
|
+
atX:x
|
|
84
|
+
y:y];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
- (void)drawOrderedMarkerAtX:(CGFloat)rightBoundaryX attrs:(NSDictionary *)attrs baselineY:(CGFloat)baselineY
|
|
88
|
+
{
|
|
89
|
+
NSNumber *num = attrs[ListItemNumberAttribute];
|
|
90
|
+
if (!num)
|
|
91
|
+
return;
|
|
92
|
+
|
|
93
|
+
NSString *text = [NSString stringWithFormat:@"%ld.", (long)num.integerValue];
|
|
94
|
+
UIFont *font = [_config listMarkerFont] ?: [self defaultFont];
|
|
95
|
+
|
|
96
|
+
NSDictionary *mAttrs = @{
|
|
97
|
+
NSFontAttributeName : font,
|
|
98
|
+
NSForegroundColorAttributeName : [_config listStyleMarkerColor] ?: [UIColor blackColor]
|
|
99
|
+
};
|
|
100
|
+
CGSize size = [text sizeWithAttributes:mAttrs];
|
|
101
|
+
|
|
102
|
+
if ([self isValidX:rightBoundaryX - size.width y:baselineY]) {
|
|
103
|
+
[text drawAtPoint:CGPointMake(rightBoundaryX - size.width, baselineY - font.ascender) withAttributes:mAttrs];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
- (void)executeDrawing:(void (^)(CGContextRef))block atX:(CGFloat)x y:(CGFloat)y
|
|
108
|
+
{
|
|
109
|
+
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
|
110
|
+
if (ctx && [self isValidX:x y:y]) {
|
|
111
|
+
CGContextSaveGState(ctx);
|
|
112
|
+
block(ctx);
|
|
113
|
+
CGContextRestoreGState(ctx);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
- (UIFont *)defaultFont
|
|
118
|
+
{
|
|
119
|
+
return [UIFont systemFontOfSize:[_config listStyleFontSize]];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
- (BOOL)isValidX:(CGFloat)x y:(CGFloat)y
|
|
123
|
+
{
|
|
124
|
+
return !isnan(x) && !isinf(x) && !isnan(y) && !isinf(y);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#import <UIKit/UIKit.h>
|
|
2
|
+
|
|
3
|
+
@class AccessibilityInfo;
|
|
4
|
+
|
|
5
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Builds UIAccessibilityElement objects from markdown content for VoiceOver.
|
|
9
|
+
* Handles headings, links, images, lists, and custom rotor navigation.
|
|
10
|
+
*/
|
|
11
|
+
@interface MarkdownAccessibilityElementBuilder : NSObject
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Builds accessibility elements for the given text view content.
|
|
15
|
+
* - Headings get UIAccessibilityTraitHeader
|
|
16
|
+
* - Links get UIAccessibilityTraitLink
|
|
17
|
+
* - Images get UIAccessibilityTraitImage
|
|
18
|
+
* - List items get position hints (e.g., "bullet point", "list item 1")
|
|
19
|
+
* - Other content gets UIAccessibilityTraitStaticText
|
|
20
|
+
*/
|
|
21
|
+
+ (NSMutableArray<UIAccessibilityElement *> *)buildElementsForTextView:(UITextView *)textView
|
|
22
|
+
info:(AccessibilityInfo *)info
|
|
23
|
+
container:(id)container;
|
|
24
|
+
|
|
25
|
+
/** Filters elements with UIAccessibilityTraitHeader trait. */
|
|
26
|
+
+ (NSArray<UIAccessibilityElement *> *)filterHeadingElements:(NSArray<UIAccessibilityElement *> *)elements;
|
|
27
|
+
|
|
28
|
+
/** Filters elements with UIAccessibilityTraitLink trait. */
|
|
29
|
+
+ (NSArray<UIAccessibilityElement *> *)filterLinkElements:(NSArray<UIAccessibilityElement *> *)elements;
|
|
30
|
+
|
|
31
|
+
/** Filters elements with UIAccessibilityTraitImage trait. */
|
|
32
|
+
+ (NSArray<UIAccessibilityElement *> *)filterImageElements:(NSArray<UIAccessibilityElement *> *)elements;
|
|
33
|
+
|
|
34
|
+
/** Creates a custom rotor for heading navigation. */
|
|
35
|
+
+ (UIAccessibilityCustomRotor *)createHeadingRotorWithElements:(NSArray<UIAccessibilityElement *> *)elements;
|
|
36
|
+
|
|
37
|
+
/** Creates a custom rotor for link navigation. */
|
|
38
|
+
+ (UIAccessibilityCustomRotor *)createLinkRotorWithElements:(NSArray<UIAccessibilityElement *> *)elements;
|
|
39
|
+
|
|
40
|
+
/** Creates a custom rotor for image navigation. */
|
|
41
|
+
+ (UIAccessibilityCustomRotor *)createImageRotorWithElements:(NSArray<UIAccessibilityElement *> *)elements;
|
|
42
|
+
|
|
43
|
+
@end
|
|
44
|
+
|
|
45
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
#import "MarkdownAccessibilityElementBuilder.h"
|
|
2
|
+
#import "AccessibilityInfo.h"
|
|
3
|
+
|
|
4
|
+
typedef NS_ENUM(NSInteger, ElementType) { ElementTypeText, ElementTypeLink, ElementTypeImage };
|
|
5
|
+
|
|
6
|
+
@implementation MarkdownAccessibilityElementBuilder
|
|
7
|
+
|
|
8
|
+
#pragma mark - Public API
|
|
9
|
+
|
|
10
|
+
+ (NSMutableArray<UIAccessibilityElement *> *)buildElementsForTextView:(UITextView *)textView
|
|
11
|
+
info:(AccessibilityInfo *)info
|
|
12
|
+
container:(id)container
|
|
13
|
+
{
|
|
14
|
+
NSString *fullString = textView.attributedText.string;
|
|
15
|
+
if (fullString.length == 0)
|
|
16
|
+
return [NSMutableArray array];
|
|
17
|
+
|
|
18
|
+
[textView.layoutManager ensureLayoutForTextContainer:textView.textContainer];
|
|
19
|
+
|
|
20
|
+
NSMutableArray<UIAccessibilityElement *> *elements = [NSMutableArray array];
|
|
21
|
+
NSUInteger currentPos = 0;
|
|
22
|
+
|
|
23
|
+
while (currentPos < fullString.length) {
|
|
24
|
+
NSRange paragraphRange = [fullString paragraphRangeForRange:NSMakeRange(currentPos, 0)];
|
|
25
|
+
NSString *trimmed = [[fullString substringWithRange:paragraphRange]
|
|
26
|
+
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
27
|
+
|
|
28
|
+
if (trimmed.length > 0) {
|
|
29
|
+
NSArray *links = [self linksInRange:paragraphRange info:info];
|
|
30
|
+
NSArray *images = [self imagesInRange:paragraphRange info:info];
|
|
31
|
+
NSArray *specials = [links arrayByAddingObjectsFromArray:images];
|
|
32
|
+
|
|
33
|
+
NSInteger level = [self headingLevelForRange:paragraphRange info:info];
|
|
34
|
+
NSDictionary *list = [self listItemInfoForRange:paragraphRange info:info];
|
|
35
|
+
|
|
36
|
+
if (specials.count == 0) {
|
|
37
|
+
[self addTextElementsPerLineTo:elements
|
|
38
|
+
range:paragraphRange
|
|
39
|
+
fullText:fullString
|
|
40
|
+
heading:level
|
|
41
|
+
listInfo:list
|
|
42
|
+
view:textView
|
|
43
|
+
container:container];
|
|
44
|
+
} else {
|
|
45
|
+
[elements addObjectsFromArray:[self segmentedElementsForParagraph:paragraphRange
|
|
46
|
+
fullText:fullString
|
|
47
|
+
headingLevel:level
|
|
48
|
+
listInfo:list
|
|
49
|
+
specials:specials
|
|
50
|
+
inTextView:textView
|
|
51
|
+
container:container]];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
currentPos = NSMaxRange(paragraphRange);
|
|
55
|
+
}
|
|
56
|
+
return elements;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#pragma mark - Segmentation
|
|
60
|
+
|
|
61
|
+
+ (NSArray<UIAccessibilityElement *> *)segmentedElementsForParagraph:(NSRange)paragraphRange
|
|
62
|
+
fullText:(NSString *)fullText
|
|
63
|
+
headingLevel:(NSInteger)headingLevel
|
|
64
|
+
listInfo:(NSDictionary *)listInfo
|
|
65
|
+
specials:(NSArray *)specials
|
|
66
|
+
inTextView:(UITextView *)textView
|
|
67
|
+
container:(id)container
|
|
68
|
+
{
|
|
69
|
+
NSMutableArray<UIAccessibilityElement *> *elements = [NSMutableArray array];
|
|
70
|
+
NSArray *sortedSpecials = [specials sortedArrayUsingComparator:^NSComparisonResult(NSDictionary *a, NSDictionary *b) {
|
|
71
|
+
return [@([a[@"range"] rangeValue].location) compare:@([b[@"range"] rangeValue].location)];
|
|
72
|
+
}];
|
|
73
|
+
|
|
74
|
+
NSUInteger segmentStart = paragraphRange.location;
|
|
75
|
+
for (NSDictionary *item in sortedSpecials) {
|
|
76
|
+
NSRange itemRange = [item[@"range"] rangeValue];
|
|
77
|
+
|
|
78
|
+
if (itemRange.location > segmentStart) {
|
|
79
|
+
NSRange beforeRange = NSMakeRange(segmentStart, itemRange.location - segmentStart);
|
|
80
|
+
[self addTextElementsPerLineTo:elements
|
|
81
|
+
range:beforeRange
|
|
82
|
+
fullText:fullText
|
|
83
|
+
heading:headingLevel
|
|
84
|
+
listInfo:listInfo
|
|
85
|
+
view:textView
|
|
86
|
+
container:container];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
BOOL isImg = item[@"altText"] != nil;
|
|
90
|
+
NSString *label = isImg ? item[@"altText"] : [fullText substringWithRange:itemRange];
|
|
91
|
+
[elements addObject:[self createElementForRange:itemRange
|
|
92
|
+
type:isImg ? ElementTypeImage : ElementTypeLink
|
|
93
|
+
text:label
|
|
94
|
+
isLinked:isImg ? [item[@"isLinked"] boolValue] : YES
|
|
95
|
+
heading:0
|
|
96
|
+
listInfo:listInfo
|
|
97
|
+
view:textView
|
|
98
|
+
container:container]];
|
|
99
|
+
segmentStart = NSMaxRange(itemRange);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (segmentStart < NSMaxRange(paragraphRange)) {
|
|
103
|
+
NSRange afterRange = NSMakeRange(segmentStart, NSMaxRange(paragraphRange) - segmentStart);
|
|
104
|
+
[self addTextElementsPerLineTo:elements
|
|
105
|
+
range:afterRange
|
|
106
|
+
fullText:fullText
|
|
107
|
+
heading:headingLevel
|
|
108
|
+
listInfo:listInfo
|
|
109
|
+
view:textView
|
|
110
|
+
container:container];
|
|
111
|
+
}
|
|
112
|
+
return elements;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
#pragma mark - Factory & Precise Splitting
|
|
116
|
+
|
|
117
|
+
+ (UIAccessibilityElement *)createElementForRange:(NSRange)range
|
|
118
|
+
type:(ElementType)type
|
|
119
|
+
text:(NSString *)text
|
|
120
|
+
isLinked:(BOOL)linked
|
|
121
|
+
heading:(NSInteger)level
|
|
122
|
+
listInfo:(NSDictionary *)listInfo
|
|
123
|
+
view:(UITextView *)tv
|
|
124
|
+
container:(id)c
|
|
125
|
+
{
|
|
126
|
+
UIAccessibilityElement *el = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:c];
|
|
127
|
+
el.accessibilityLabel = (type == ElementTypeImage && text.length == 0) ? NSLocalizedString(@"Image", @"") : text;
|
|
128
|
+
el.accessibilityFrameInContainerSpace = [self frameForRange:range inTextView:tv container:c];
|
|
129
|
+
|
|
130
|
+
NSMutableArray *values = [NSMutableArray array];
|
|
131
|
+
|
|
132
|
+
if (type == ElementTypeImage) {
|
|
133
|
+
el.accessibilityTraits =
|
|
134
|
+
linked ? (UIAccessibilityTraitImage | UIAccessibilityTraitLink) : UIAccessibilityTraitImage;
|
|
135
|
+
} else if (type == ElementTypeLink) {
|
|
136
|
+
el.accessibilityTraits = UIAccessibilityTraitLink;
|
|
137
|
+
} else if (level > 0) {
|
|
138
|
+
el.accessibilityTraits = UIAccessibilityTraitHeader;
|
|
139
|
+
[values addObject:[NSString stringWithFormat:NSLocalizedString(@"heading level %ld", @""), (long)level]];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (el.accessibilityTraits & UIAccessibilityTraitLink) {
|
|
143
|
+
el.accessibilityHint = NSLocalizedString(@"Tap to open link", @"");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Append List Info to values if it exists
|
|
147
|
+
if (listInfo && type != ElementTypeImage) {
|
|
148
|
+
[values addObject:[self formatListAnnouncement:listInfo]];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Combine all values (Heading Level + List Position) into one string
|
|
152
|
+
if (values.count > 0) {
|
|
153
|
+
el.accessibilityValue = [values componentsJoinedByString:@", "];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return el;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
+ (void)addTextElementsPerLineTo:(NSMutableArray *)elements
|
|
160
|
+
range:(NSRange)range
|
|
161
|
+
fullText:(NSString *)fullText
|
|
162
|
+
heading:(NSInteger)level
|
|
163
|
+
listInfo:(NSDictionary *)listInfo
|
|
164
|
+
view:(UITextView *)tv
|
|
165
|
+
container:(id)c
|
|
166
|
+
{
|
|
167
|
+
NSLayoutManager *lm = tv.layoutManager;
|
|
168
|
+
NSRange glyphRange = [lm glyphRangeForCharacterRange:range actualCharacterRange:NULL];
|
|
169
|
+
|
|
170
|
+
[lm enumerateLineFragmentsForGlyphRange:glyphRange
|
|
171
|
+
usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer *tc, NSRange lineGlyphRange,
|
|
172
|
+
BOOL *stop) {
|
|
173
|
+
NSRange intersection = NSIntersectionRange(glyphRange, lineGlyphRange);
|
|
174
|
+
if (intersection.length > 0) {
|
|
175
|
+
NSRange charRange = [lm characterRangeForGlyphRange:intersection
|
|
176
|
+
actualGlyphRange:NULL];
|
|
177
|
+
NSString *trimmed = [[fullText substringWithRange:charRange]
|
|
178
|
+
stringByTrimmingCharactersInSet:[NSCharacterSet
|
|
179
|
+
whitespaceAndNewlineCharacterSet]];
|
|
180
|
+
if (trimmed.length > 0) {
|
|
181
|
+
[elements addObject:[self createElementForRange:charRange
|
|
182
|
+
type:ElementTypeText
|
|
183
|
+
text:trimmed
|
|
184
|
+
isLinked:NO
|
|
185
|
+
heading:level
|
|
186
|
+
listInfo:listInfo
|
|
187
|
+
view:tv
|
|
188
|
+
container:c]];
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
#pragma mark - Helpers
|
|
195
|
+
|
|
196
|
+
+ (NSString *)formatListAnnouncement:(NSDictionary *)info
|
|
197
|
+
{
|
|
198
|
+
NSString *prefix = [info[@"depth"] integerValue] > 1 ? @"nested " : @"";
|
|
199
|
+
return [info[@"isOrdered"] boolValue]
|
|
200
|
+
? [NSString stringWithFormat:@"%@list item %ld", prefix, (long)[info[@"position"] integerValue]]
|
|
201
|
+
: [NSString stringWithFormat:@"%@bullet point", prefix];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
+ (CGRect)frameForRange:(NSRange)range inTextView:(UITextView *)textView container:(id)container
|
|
205
|
+
{
|
|
206
|
+
NSRange glyphRange = [textView.layoutManager glyphRangeForCharacterRange:range actualCharacterRange:NULL];
|
|
207
|
+
CGRect rect = [textView.layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textView.textContainer];
|
|
208
|
+
rect = CGRectOffset(CGRectInset(rect, -2, -2), textView.textContainerInset.left, textView.textContainerInset.top);
|
|
209
|
+
return [(UIView *)container convertRect:CGRectIntegral(rect) fromView:textView];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
#pragma mark - Data Helpers
|
|
213
|
+
|
|
214
|
+
+ (NSInteger)headingLevelForRange:(NSRange)range info:(AccessibilityInfo *)info
|
|
215
|
+
{
|
|
216
|
+
for (NSUInteger i = 0; i < info.headingRanges.count; i++) {
|
|
217
|
+
if (NSIntersectionRange(range, [info.headingRanges[i] rangeValue]).length > 0)
|
|
218
|
+
return [info.headingLevels[i] integerValue];
|
|
219
|
+
}
|
|
220
|
+
return 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
+ (NSArray *)linksInRange:(NSRange)range info:(AccessibilityInfo *)info
|
|
224
|
+
{
|
|
225
|
+
NSMutableArray *links = [NSMutableArray array];
|
|
226
|
+
for (NSUInteger i = 0; i < info.linkRanges.count; i++) {
|
|
227
|
+
if (NSIntersectionRange(range, [info.linkRanges[i] rangeValue]).length > 0) {
|
|
228
|
+
[links addObject:@{@"range" : info.linkRanges[i], @"url" : info.linkURLs[i] ?: @""}];
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return links;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
+ (NSArray *)imagesInRange:(NSRange)range info:(AccessibilityInfo *)info
|
|
235
|
+
{
|
|
236
|
+
NSMutableArray *images = [NSMutableArray array];
|
|
237
|
+
for (NSUInteger i = 0; i < info.imageRanges.count; i++) {
|
|
238
|
+
NSRange imgRange = [info.imageRanges[i] rangeValue];
|
|
239
|
+
if (NSIntersectionRange(range, imgRange).length > 0) {
|
|
240
|
+
BOOL linked = NO;
|
|
241
|
+
for (NSValue *val in info.linkRanges)
|
|
242
|
+
if (NSIntersectionRange(imgRange, val.rangeValue).length > 0) {
|
|
243
|
+
linked = YES;
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
[images addObject:@{
|
|
247
|
+
@"range" : info.imageRanges[i],
|
|
248
|
+
@"altText" : info.imageAltTexts[i] ?: @"",
|
|
249
|
+
@"isLinked" : @(linked)
|
|
250
|
+
}];
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return images;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
+ (NSDictionary *)listItemInfoForRange:(NSRange)range info:(AccessibilityInfo *)info
|
|
257
|
+
{
|
|
258
|
+
if (!info)
|
|
259
|
+
return nil;
|
|
260
|
+
for (NSUInteger i = 0; i < info.listItemRanges.count; i++) {
|
|
261
|
+
if (NSIntersectionRange(range, [info.listItemRanges[i] rangeValue]).length > 0) {
|
|
262
|
+
return @{
|
|
263
|
+
@"position" : info.listItemPositions[i],
|
|
264
|
+
@"depth" : info.listItemDepths[i],
|
|
265
|
+
@"isOrdered" : info.listItemOrdered[i]
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return nil;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
#pragma mark - Rotors
|
|
273
|
+
|
|
274
|
+
+ (NSArray *)filterElements:(NSArray *)els withTrait:(UIAccessibilityTraits)trait
|
|
275
|
+
{
|
|
276
|
+
return [els filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(UIAccessibilityElement *el, id b) {
|
|
277
|
+
return (el.accessibilityTraits & trait) != 0;
|
|
278
|
+
}]];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
+ (UIAccessibilityCustomRotor *)createRotorWithName:(NSString *)name elements:(NSArray *)els
|
|
282
|
+
{
|
|
283
|
+
return [[UIAccessibilityCustomRotor alloc]
|
|
284
|
+
initWithName:name
|
|
285
|
+
itemSearchBlock:^UIAccessibilityCustomRotorItemResult *(UIAccessibilityCustomRotorSearchPredicate *p) {
|
|
286
|
+
if (els.count == 0)
|
|
287
|
+
return nil;
|
|
288
|
+
NSInteger idx = p.currentItem.targetElement ? [els indexOfObject:p.currentItem.targetElement] : NSNotFound;
|
|
289
|
+
NSInteger next = (p.searchDirection == UIAccessibilityCustomRotorDirectionNext)
|
|
290
|
+
? (idx == NSNotFound ? 0 : idx + 1)
|
|
291
|
+
: (idx == NSNotFound ? els.count - 1 : idx - 1);
|
|
292
|
+
return (next >= 0 && next < els.count)
|
|
293
|
+
? [[UIAccessibilityCustomRotorItemResult alloc] initWithTargetElement:els[next] targetRange:nil]
|
|
294
|
+
: nil;
|
|
295
|
+
}];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
+ (NSArray<UIAccessibilityElement *> *)filterHeadingElements:(NSArray *)els
|
|
299
|
+
{
|
|
300
|
+
return [self filterElements:els withTrait:UIAccessibilityTraitHeader];
|
|
301
|
+
}
|
|
302
|
+
+ (NSArray<UIAccessibilityElement *> *)filterLinkElements:(NSArray *)els
|
|
303
|
+
{
|
|
304
|
+
return [self filterElements:els withTrait:UIAccessibilityTraitLink];
|
|
305
|
+
}
|
|
306
|
+
+ (NSArray<UIAccessibilityElement *> *)filterImageElements:(NSArray *)els
|
|
307
|
+
{
|
|
308
|
+
return [self filterElements:els withTrait:UIAccessibilityTraitImage];
|
|
309
|
+
}
|
|
310
|
+
+ (UIAccessibilityCustomRotor *)createHeadingRotorWithElements:(NSArray *)els
|
|
311
|
+
{
|
|
312
|
+
return [self createRotorWithName:NSLocalizedString(@"Headings", @"") elements:els];
|
|
313
|
+
}
|
|
314
|
+
+ (UIAccessibilityCustomRotor *)createLinkRotorWithElements:(NSArray *)els
|
|
315
|
+
{
|
|
316
|
+
return [self createRotorWithName:NSLocalizedString(@"Links", @"") elements:els];
|
|
317
|
+
}
|
|
318
|
+
+ (UIAccessibilityCustomRotor *)createImageRotorWithElements:(NSArray *)els
|
|
319
|
+
{
|
|
320
|
+
return [self createRotorWithName:NSLocalizedString(@"Images", @"") elements:els];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
@end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#import <UIKit/UIKit.h>
|
|
3
|
+
|
|
4
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
5
|
+
|
|
6
|
+
#ifdef __cplusplus
|
|
7
|
+
extern "C" {
|
|
8
|
+
#endif
|
|
9
|
+
|
|
10
|
+
/// Extracts markdown from an attributed string (best-effort reconstruction).
|
|
11
|
+
NSString *_Nullable extractMarkdownFromAttributedString(NSAttributedString *attributedText, NSRange range);
|
|
12
|
+
|
|
13
|
+
#ifdef __cplusplus
|
|
14
|
+
}
|
|
15
|
+
#endif
|
|
16
|
+
|
|
17
|
+
NS_ASSUME_NONNULL_END
|