react-native-enriched-markdown 0.1.1 → 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/README.md +80 -8
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +17 -2
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +6 -1
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +9 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +6 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +28 -3
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +225 -1
- package/android/src/main/cpp/jni-adapter.cpp +28 -11
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +132 -15
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextLayoutManager.kt +1 -16
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +67 -13
- package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +241 -21
- 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/parser/MarkdownASTNode.kt +2 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/parser/Parser.kt +17 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt +13 -18
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeBlockRenderer.kt +23 -24
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeRenderer.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/DocumentRenderer.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/EmphasisRenderer.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/HeadingRenderer.kt +18 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ImageRenderer.kt +22 -6
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LineBreakRenderer.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +3 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListItemRenderer.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListRenderer.kt +16 -9
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/NodeRenderer.kt +5 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ParagraphRenderer.kt +23 -9
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/Renderer.kt +24 -10
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +1 -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 +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/TextRenderer.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ThematicBreakRenderer.kt +1 -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/ImageSpan.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LineHeightSpan.kt +8 -17
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +19 -5
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/MarginBottomSpan.kt +1 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/StrikethroughSpan.kt +12 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/BaseBlockStyle.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/BlockquoteStyle.kt +3 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeBlockStyle.kt +3 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/HeadingStyle.kt +5 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ImageStyle.kt +3 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt +3 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ParagraphStyle.kt +5 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StrikethroughStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt +32 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleParser.kt +22 -5
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/TextAlignment.kt +32 -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/HTMLGenerator.kt +23 -5
- 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 +10 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/Utils.kt +58 -56
- package/android/src/main/jni/CMakeLists.txt +1 -13
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.cpp +0 -13
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.h +2 -14
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/conversions.h +3 -0
- package/cpp/parser/MD4CParser.cpp +21 -8
- package/cpp/parser/MD4CParser.hpp +5 -1
- package/cpp/parser/MarkdownASTNode.hpp +2 -0
- package/ios/EnrichedMarkdownText.mm +356 -29
- package/ios/attachments/{ImageAttachment.h → EnrichedMarkdownImageAttachment.h} +1 -1
- package/ios/attachments/{ImageAttachment.m → EnrichedMarkdownImageAttachment.m} +4 -4
- package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.cpp +9 -0
- package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.h +6 -0
- package/ios/generated/EnrichedMarkdownTextSpec/Props.cpp +28 -3
- package/ios/generated/EnrichedMarkdownTextSpec/Props.h +225 -1
- package/ios/parser/MarkdownASTNode.h +2 -0
- package/ios/parser/MarkdownParser.h +9 -0
- package/ios/parser/MarkdownParser.mm +31 -2
- package/ios/parser/MarkdownParserBridge.mm +13 -3
- package/ios/renderer/AttributedRenderer.h +2 -0
- package/ios/renderer/AttributedRenderer.m +52 -19
- package/ios/renderer/BlockquoteRenderer.m +7 -6
- package/ios/renderer/CodeBlockRenderer.m +9 -8
- package/ios/renderer/HeadingRenderer.m +31 -24
- package/ios/renderer/ImageRenderer.m +31 -10
- package/ios/renderer/ListItemRenderer.m +51 -39
- package/ios/renderer/ListRenderer.m +21 -18
- package/ios/renderer/ParagraphRenderer.m +27 -16
- package/ios/renderer/RenderContext.h +17 -0
- package/ios/renderer/RenderContext.m +66 -2
- package/ios/renderer/RendererFactory.m +6 -0
- package/ios/renderer/StrikethroughRenderer.h +6 -0
- package/ios/renderer/StrikethroughRenderer.m +40 -0
- package/ios/renderer/UnderlineRenderer.h +6 -0
- package/ios/renderer/UnderlineRenderer.m +39 -0
- package/ios/styles/StyleConfig.h +46 -0
- package/ios/styles/StyleConfig.mm +351 -12
- package/ios/utils/AccessibilityInfo.h +35 -0
- package/ios/utils/AccessibilityInfo.m +24 -0
- package/ios/utils/CodeBlockBackground.m +4 -9
- package/ios/utils/FontUtils.h +5 -0
- package/ios/utils/FontUtils.m +14 -0
- package/ios/utils/HTMLGenerator.m +21 -7
- package/ios/utils/MarkdownAccessibilityElementBuilder.h +45 -0
- package/ios/utils/MarkdownAccessibilityElementBuilder.m +323 -0
- package/ios/utils/MarkdownExtractor.m +18 -5
- package/ios/utils/ParagraphStyleUtils.h +10 -2
- package/ios/utils/ParagraphStyleUtils.m +57 -2
- package/ios/utils/PasteboardUtils.h +1 -1
- package/ios/utils/PasteboardUtils.m +3 -3
- package/lib/module/EnrichedMarkdownText.js +33 -2
- package/lib/module/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +83 -3
- package/lib/module/index.js +0 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/normalizeMarkdownStyle.js +58 -14
- package/lib/module/normalizeMarkdownStyle.js.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownText.d.ts +85 -3
- package/lib/typescript/src/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +75 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/EnrichedMarkdownText.tsx +133 -5
- package/src/EnrichedMarkdownTextNativeComponent.ts +83 -3
- package/src/index.tsx +5 -2
- package/src/normalizeMarkdownStyle.ts +46 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.cpp +0 -9
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.h +0 -25
|
@@ -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
|
|
@@ -50,22 +50,17 @@
|
|
|
50
50
|
blockRect.origin.y += origin.y;
|
|
51
51
|
blockRect.size.width = textContainer.size.width;
|
|
52
52
|
|
|
53
|
-
//
|
|
54
|
-
//
|
|
53
|
+
// We extend the background specifically to cover the bottom padding spacer,
|
|
54
|
+
// excluding any additional marginBottom applied via measurement.
|
|
55
55
|
BOOL isLastCodeBlock = (NSMaxRange(range) == layoutManager.textStorage.length);
|
|
56
56
|
if (isLastCodeBlock) {
|
|
57
|
-
|
|
58
|
-
if (viewHeight > 0 && viewHeight < CGFLOAT_MAX) {
|
|
59
|
-
blockRect.size.height = viewHeight - blockRect.origin.y + origin.y;
|
|
60
|
-
} else {
|
|
61
|
-
// Fallback: add padding if container height not set
|
|
62
|
-
blockRect.size.height += [_config codeBlockPadding];
|
|
63
|
-
}
|
|
57
|
+
blockRect.size.height += [_config codeBlockPadding];
|
|
64
58
|
}
|
|
65
59
|
|
|
66
60
|
CGFloat borderWidth = [_config codeBlockBorderWidth];
|
|
67
61
|
CGFloat borderRadius = [_config codeBlockBorderRadius];
|
|
68
62
|
CGFloat inset = borderWidth / 2.0;
|
|
63
|
+
|
|
69
64
|
CGRect insetRect = CGRectInset(blockRect, inset, inset);
|
|
70
65
|
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:insetRect cornerRadius:MAX(0, borderRadius - inset)];
|
|
71
66
|
|
package/ios/utils/FontUtils.h
CHANGED
|
@@ -13,6 +13,11 @@ extern "C" {
|
|
|
13
13
|
/** Returns a cached UIFont from BlockStyle properties via RenderContext. */
|
|
14
14
|
extern UIFont *cachedFontFromBlockStyle(BlockStyle *blockStyle, RenderContext *context);
|
|
15
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
|
+
|
|
16
21
|
#ifdef __cplusplus
|
|
17
22
|
}
|
|
18
23
|
#endif
|
package/ios/utils/FontUtils.m
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#import "FontUtils.h"
|
|
2
2
|
#import "RenderContext.h"
|
|
3
|
+
#import <React/RCTUtils.h>
|
|
3
4
|
|
|
4
5
|
UIFont *cachedFontFromBlockStyle(BlockStyle *blockStyle, RenderContext *context)
|
|
5
6
|
{
|
|
@@ -11,3 +12,16 @@ UIFont *cachedFontFromBlockStyle(BlockStyle *blockStyle, RenderContext *context)
|
|
|
11
12
|
}
|
|
12
13
|
return [context cachedFontForSize:blockStyle.fontSize family:blockStyle.fontFamily weight:blockStyle.fontWeight];
|
|
13
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
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#import "HTMLGenerator.h"
|
|
2
2
|
#import "BlockquoteBorder.h"
|
|
3
3
|
#import "CodeBackground.h"
|
|
4
|
-
#import "
|
|
4
|
+
#import "EnrichedMarkdownImageAttachment.h"
|
|
5
5
|
#import "LastElementUtils.h"
|
|
6
6
|
#import "ListItemRenderer.h"
|
|
7
7
|
#import "RenderContext.h"
|
|
@@ -100,6 +100,8 @@ typedef struct {
|
|
|
100
100
|
@property (nonatomic) CGFloat thematicBreakHeight;
|
|
101
101
|
@property (nonatomic) CGFloat thematicBreakMarginTop;
|
|
102
102
|
@property (nonatomic) CGFloat thematicBreakMarginBottom;
|
|
103
|
+
@property (nonatomic, copy) NSString *strikethroughColor;
|
|
104
|
+
@property (nonatomic, copy) NSString *underlineColor;
|
|
103
105
|
@end
|
|
104
106
|
|
|
105
107
|
@implementation CachedStyles
|
|
@@ -276,6 +278,8 @@ static CachedStyles *cacheStyles(StyleConfig *styleConfig)
|
|
|
276
278
|
cache.thematicBreakHeight = [styleConfig thematicBreakHeight];
|
|
277
279
|
cache.thematicBreakMarginTop = [styleConfig thematicBreakMarginTop];
|
|
278
280
|
cache.thematicBreakMarginBottom = [styleConfig thematicBreakMarginBottom];
|
|
281
|
+
cache.strikethroughColor = colorToCSS([styleConfig strikethroughColor]);
|
|
282
|
+
cache.underlineColor = colorToCSS([styleConfig underlineColor]);
|
|
279
283
|
|
|
280
284
|
return cache;
|
|
281
285
|
}
|
|
@@ -430,8 +434,8 @@ static void generateInlineHTML(NSMutableString *html, NSAttributedString *attrib
|
|
|
430
434
|
|
|
431
435
|
if ([text containsString:kObjectReplacementChar]) {
|
|
432
436
|
id attachment = attrs[NSAttachmentAttributeName];
|
|
433
|
-
if ([attachment isKindOfClass:[
|
|
434
|
-
|
|
437
|
+
if ([attachment isKindOfClass:[EnrichedMarkdownImageAttachment class]]) {
|
|
438
|
+
EnrichedMarkdownImageAttachment *img = (EnrichedMarkdownImageAttachment *)attachment;
|
|
435
439
|
if (img.imageURL) {
|
|
436
440
|
if (img.isInline) {
|
|
437
441
|
[html appendFormat:
|
|
@@ -501,10 +505,20 @@ static void generateInlineHTML(NSMutableString *html, NSAttributedString *attrib
|
|
|
501
505
|
}
|
|
502
506
|
}
|
|
503
507
|
|
|
504
|
-
if ([strikethrough integerValue] > 0)
|
|
505
|
-
[
|
|
506
|
-
|
|
507
|
-
|
|
508
|
+
if ([strikethrough integerValue] > 0) {
|
|
509
|
+
if (styles.strikethroughColor && ![styles.strikethroughColor isEqualToString:@"inherit"]) {
|
|
510
|
+
[html appendFormat:@"<s style=\"text-decoration-color: %@;\">", styles.strikethroughColor];
|
|
511
|
+
} else {
|
|
512
|
+
[html appendString:@"<s>"];
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
if ([underline integerValue] > 0 && !linkAttr) {
|
|
516
|
+
if (styles.underlineColor && ![styles.underlineColor isEqualToString:@"inherit"]) {
|
|
517
|
+
[html appendFormat:@"<u style=\"text-decoration-color: %@;\">", styles.underlineColor];
|
|
518
|
+
} else {
|
|
519
|
+
[html appendString:@"<u>"];
|
|
520
|
+
}
|
|
521
|
+
}
|
|
508
522
|
|
|
509
523
|
[html appendString:escapeHTML(text)];
|
|
510
524
|
|
|
@@ -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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#import "MarkdownExtractor.h"
|
|
2
2
|
#import "BlockquoteBorder.h"
|
|
3
|
-
#import "
|
|
3
|
+
#import "EnrichedMarkdownImageAttachment.h"
|
|
4
4
|
#import "LastElementUtils.h"
|
|
5
5
|
#import "RuntimeKeys.h"
|
|
6
6
|
#import "ThematicBreakAttachment.h"
|
|
@@ -67,7 +67,8 @@ static void extractFontTraits(NSDictionary *attrs, BOOL *isBold, BOOL *isItalic,
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
static NSString *applyInlineFormatting(NSString *text, BOOL isBold, BOOL isItalic, BOOL isMonospace,
|
|
70
|
+
static NSString *applyInlineFormatting(NSString *text, BOOL isBold, BOOL isItalic, BOOL isMonospace,
|
|
71
|
+
BOOL isStrikethrough, BOOL isUnderline, NSString *linkURL)
|
|
71
72
|
{
|
|
72
73
|
NSMutableString *result = [NSMutableString stringWithString:text];
|
|
73
74
|
|
|
@@ -75,6 +76,12 @@ static NSString *applyInlineFormatting(NSString *text, BOOL isBold, BOOL isItali
|
|
|
75
76
|
if (isMonospace && !linkURL) {
|
|
76
77
|
result = [NSMutableString stringWithFormat:@"`%@`", result];
|
|
77
78
|
}
|
|
79
|
+
if (isStrikethrough) {
|
|
80
|
+
result = [NSMutableString stringWithFormat:@"~~%@~~", result];
|
|
81
|
+
}
|
|
82
|
+
if (isUnderline && !linkURL) {
|
|
83
|
+
result = [NSMutableString stringWithFormat:@"<u>%@</u>", result];
|
|
84
|
+
}
|
|
78
85
|
if (isItalic) {
|
|
79
86
|
result = [NSMutableString stringWithFormat:@"*%@*", result];
|
|
80
87
|
}
|
|
@@ -128,8 +135,8 @@ NSString *_Nullable extractMarkdownFromAttributedString(NSAttributedString *attr
|
|
|
128
135
|
|
|
129
136
|
// Images and Thematic Breaks
|
|
130
137
|
NSTextAttachment *attachment = attrs[NSAttachmentAttributeName];
|
|
131
|
-
if ([attachment isKindOfClass:[
|
|
132
|
-
|
|
138
|
+
if ([attachment isKindOfClass:[EnrichedMarkdownImageAttachment class]]) {
|
|
139
|
+
EnrichedMarkdownImageAttachment *img = (EnrichedMarkdownImageAttachment *)attachment;
|
|
133
140
|
if (!img.imageURL)
|
|
134
141
|
return;
|
|
135
142
|
|
|
@@ -255,8 +262,14 @@ NSString *_Nullable extractMarkdownFromAttributedString(NSAttributedString *attr
|
|
|
255
262
|
BOOL isBold, isItalic, isMonospace;
|
|
256
263
|
extractFontTraits(attrs, &isBold, &isItalic, &isMonospace);
|
|
257
264
|
|
|
265
|
+
NSNumber *strikethroughStyle = attrs[NSStrikethroughStyleAttributeName];
|
|
266
|
+
BOOL isStrikethrough = (strikethroughStyle != nil && [strikethroughStyle integerValue] != 0);
|
|
267
|
+
NSNumber *underlineStyle = attrs[NSUnderlineStyleAttributeName];
|
|
268
|
+
BOOL isUnderline = (underlineStyle != nil && [underlineStyle integerValue] != 0);
|
|
269
|
+
|
|
258
270
|
NSString *linkURL = attrs[NSLinkAttributeName];
|
|
259
|
-
NSString *segment = applyInlineFormatting(text, isBold, isItalic, isMonospace,
|
|
271
|
+
NSString *segment = applyInlineFormatting(text, isBold, isItalic, isMonospace, isStrikethrough,
|
|
272
|
+
isUnderline, linkURL);
|
|
260
273
|
|
|
261
274
|
// Add block prefixes at line start
|
|
262
275
|
if (isAtLineStart(result)) {
|
|
@@ -3,11 +3,19 @@
|
|
|
3
3
|
|
|
4
4
|
NS_ASSUME_NONNULL_BEGIN
|
|
5
5
|
|
|
6
|
+
__BEGIN_DECLS
|
|
7
|
+
|
|
6
8
|
extern NSAttributedString *kNewlineAttributedString;
|
|
7
9
|
|
|
8
10
|
NSMutableParagraphStyle *getOrCreateParagraphStyle(NSMutableAttributedString *output, NSUInteger index);
|
|
9
|
-
void
|
|
10
|
-
void
|
|
11
|
+
void applyParagraphSpacingAfter(NSMutableAttributedString *output, NSUInteger start, CGFloat marginBottom);
|
|
12
|
+
void applyParagraphSpacingBefore(NSMutableAttributedString *output, NSRange range, CGFloat marginTop);
|
|
13
|
+
NSUInteger applyBlockSpacingBefore(NSMutableAttributedString *output, NSUInteger insertionPoint, CGFloat marginTop);
|
|
14
|
+
void applyBlockSpacingAfter(NSMutableAttributedString *output, CGFloat marginBottom);
|
|
11
15
|
void applyLineHeight(NSMutableAttributedString *output, NSRange range, CGFloat lineHeight);
|
|
16
|
+
void applyTextAlignment(NSMutableAttributedString *output, NSRange range, NSTextAlignment textAlign);
|
|
17
|
+
NSTextAlignment textAlignmentFromString(NSString *textAlign);
|
|
18
|
+
|
|
19
|
+
__END_DECLS
|
|
12
20
|
|
|
13
21
|
NS_ASSUME_NONNULL_END
|