react-native-enriched-markdown-scaffold 1.0.2-scaffold.3 → 1.0.2-scaffold.5
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/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java +15 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java +3 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +15 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +3 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +122 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +100 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +20 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +124 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +145 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +19 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +22 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +85 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +22 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkLayoutEvent.kt +36 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/SelectionChangeEvent.kt +42 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +37 -7
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +5 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +13 -99
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/InlineChipPainter.kt +140 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkChipSpans.kt +177 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +1 -6
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkStyle.kt +36 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkVariantEntry.kt +7 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +28 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/HTMLGenerator.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/MarkdownExtractor.kt +6 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLayoutCollector.kt +100 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +4 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionChangeReporter.kt +230 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt +11 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +22 -1
- package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +8 -1
- package/docs/API_REFERENCE.md +111 -0
- package/docs/MENTIONS.md +8 -1
- package/docs/STYLES.md +27 -0
- package/docs/TEXT.md +2 -0
- package/ios/EnrichedMarkdown.mm +221 -1
- package/ios/EnrichedMarkdownText.mm +142 -1
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp +122 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h +100 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +20 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +124 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +50 -2
- package/ios/internals/MeasurementCache.h +5 -0
- package/ios/renderer/LinkRenderer.m +47 -15
- package/ios/styles/StyleConfig.h +16 -0
- package/ios/styles/StyleConfig.mm +60 -0
- package/ios/utils/CodeBackground.m +3 -65
- package/ios/utils/ENRMChipDrawing.h +24 -0
- package/ios/utils/ENRMChipDrawing.m +83 -0
- package/ios/utils/ENRMLinkChip.h +70 -0
- package/ios/utils/ENRMLinkChip.m +194 -0
- package/ios/utils/ENRMLinkLayout.h +50 -0
- package/ios/utils/ENRMLinkLayout.m +45 -0
- package/ios/utils/ENRMSelectionChange.h +96 -0
- package/ios/utils/ENRMSelectionChange.m +204 -0
- package/ios/utils/EditMenuUtils+macOS.m +4 -1
- package/ios/utils/EditMenuUtils.m +4 -1
- package/ios/utils/HTMLGenerator.m +2 -1
- package/ios/utils/MarkdownAccessibilityElementBuilder.m +3 -1
- package/ios/utils/MarkdownExtractor.m +2 -1
- package/ios/utils/RTFExportUtils.m +2 -0
- package/ios/utils/StylePropsUtils.h +37 -1
- package/ios/utils/TextViewLayoutManager.mm +3 -0
- package/ios/views/ENRMCodeBlockContainerView.h +3 -0
- package/ios/views/ENRMCodeBlockContainerView.m +3 -0
- package/ios/views/ENRMMathContainerView.h +3 -0
- package/ios/views/ENRMMathContainerView.m +3 -0
- package/ios/views/TableContainerView.h +6 -0
- package/ios/views/TableContainerView.m +24 -0
- package/lib/module/EnrichedMarkdownNativeComponent.ts +101 -1
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +101 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/jest/index.js +13 -0
- package/lib/module/jest/index.js.map +1 -1
- package/lib/module/native/EnrichedMarkdownText.js +138 -9
- package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/normalizeMarkdownStyle.js +13 -2
- package/lib/module/normalizeMarkdownStyle.js.map +1 -1
- package/lib/module/normalizeMarkdownStyle.web.js +13 -2
- package/lib/module/normalizeMarkdownStyle.web.js.map +1 -1
- package/lib/module/web/EnrichedMarkdownText.js +6 -3
- package/lib/module/web/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/web/renderers/InlineRenderers.js +96 -3
- package/lib/module/web/renderers/InlineRenderers.js.map +1 -1
- package/lib/module/web/styles.js +18 -0
- package/lib/module/web/styles.js.map +1 -1
- package/lib/module/web/utils.js +13 -0
- package/lib/module/web/utils.js.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +93 -2
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +93 -2
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/index.web.d.ts +1 -1
- package/lib/typescript/src/index.web.d.ts.map +1 -1
- package/lib/typescript/src/jest/index.d.ts +1 -1
- package/lib/typescript/src/jest/index.d.ts.map +1 -1
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +5 -5
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMarkdownStyle.web.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownStyle.d.ts +28 -5
- package/lib/typescript/src/types/MarkdownStyle.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownStyleInternal.d.ts +10 -0
- package/lib/typescript/src/types/MarkdownStyleInternal.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownTextProps.d.ts +76 -2
- package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownTextProps.web.d.ts +41 -1
- package/lib/typescript/src/types/MarkdownTextProps.web.d.ts.map +1 -1
- package/lib/typescript/src/types/events.d.ts +36 -0
- package/lib/typescript/src/types/events.d.ts.map +1 -1
- package/lib/typescript/src/web/EnrichedMarkdownText.d.ts +1 -1
- package/lib/typescript/src/web/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/web/renderers/InlineRenderers.d.ts.map +1 -1
- package/lib/typescript/src/web/styles.d.ts.map +1 -1
- package/lib/typescript/src/web/types.d.ts +4 -0
- package/lib/typescript/src/web/types.d.ts.map +1 -1
- package/lib/typescript/src/web/utils.d.ts +4 -0
- package/lib/typescript/src/web/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/EnrichedMarkdownNativeComponent.ts +101 -1
- package/src/EnrichedMarkdownTextNativeComponent.ts +101 -1
- package/src/index.tsx +5 -0
- package/src/index.web.tsx +5 -1
- package/src/jest/index.tsx +21 -1
- package/src/native/EnrichedMarkdownText.tsx +181 -8
- package/src/normalizeMarkdownStyle.ts +13 -0
- package/src/normalizeMarkdownStyle.web.ts +11 -0
- package/src/types/MarkdownStyle.ts +29 -5
- package/src/types/MarkdownStyleInternal.ts +10 -0
- package/src/types/MarkdownTextProps.ts +85 -1
- package/src/types/MarkdownTextProps.web.ts +43 -1
- package/src/types/events.ts +38 -0
- package/src/web/EnrichedMarkdownText.tsx +10 -3
- package/src/web/renderers/InlineRenderers.tsx +111 -3
- package/src/web/styles.ts +25 -0
- package/src/web/types.ts +4 -0
- package/src/web/utils.ts +8 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#import "LinkRenderer.h"
|
|
2
|
+
#import "ENRMLinkChip.h"
|
|
2
3
|
#import "FontUtils.h"
|
|
3
4
|
#import "RenderContext.h"
|
|
4
5
|
#import "RendererFactory.h"
|
|
@@ -13,31 +14,49 @@
|
|
|
13
14
|
into:(NSMutableAttributedString *)output
|
|
14
15
|
context:(RenderContext *)context
|
|
15
16
|
{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// 1. Render children first to establish base attributes
|
|
19
|
-
[_rendererFactory renderChildrenOfNode:node into:output context:context];
|
|
20
|
-
|
|
21
|
-
NSRange range = NSMakeRange(start, output.length - start);
|
|
22
|
-
if (range.length == 0)
|
|
23
|
-
return;
|
|
24
|
-
|
|
25
|
-
// 2. Extract configuration
|
|
17
|
+
// 1. Extract configuration
|
|
26
18
|
NSString *url = node.attributes[@"url"] ?: @"";
|
|
27
19
|
LinkVariantConfig *variant = [_config effectiveLinkVariantForURL:url];
|
|
28
20
|
|
|
29
21
|
RCTUIColor *linkColor = variant.color ?: [_config linkColor];
|
|
30
22
|
BOOL linkUnderline = variant ? variant.underline : [_config linkUnderline];
|
|
31
23
|
NSString *linkFontFamily = [_config linkFontFamily];
|
|
32
|
-
|
|
24
|
+
// Chip ("box") properties never fall back to the base link style.
|
|
25
|
+
ENRMLinkChip *chip = [[ENRMLinkChip alloc] init];
|
|
26
|
+
chip.backgroundColor = variant ? variant.backgroundColor : [_config linkBackgroundColor];
|
|
27
|
+
chip.borderColor = variant ? variant.borderColor : [_config linkBorderColor];
|
|
28
|
+
chip.borderWidth = variant ? variant.borderWidth : [_config linkBorderWidth];
|
|
29
|
+
chip.borderRadius = variant ? variant.borderRadius : [_config linkBorderRadius];
|
|
30
|
+
chip.paddingHorizontal = variant ? variant.paddingHorizontal : [_config linkPaddingHorizontal];
|
|
31
|
+
chip.paddingVertical = variant ? variant.paddingVertical : [_config linkPaddingVertical];
|
|
32
|
+
BOOL hasSpacers = chip.paddingHorizontal > 0;
|
|
33
33
|
|
|
34
34
|
NSNumber *underlineStyle = @(linkUnderline ? NSUnderlineStyleSingle : NSUnderlineStyleNone);
|
|
35
35
|
|
|
36
|
+
// 2. Render children to establish base attributes. With horizontal padding, a
|
|
37
|
+
// leading spacer goes in first so ranges the children register stay valid;
|
|
38
|
+
// its attributes are fixed up once the first child glyph exists.
|
|
39
|
+
NSUInteger start = output.length;
|
|
40
|
+
if (hasSpacers) {
|
|
41
|
+
[output appendAttributedString:ENRMLinkSpacerString([context getTextAttributes], chip.paddingHorizontal)];
|
|
42
|
+
}
|
|
43
|
+
NSUInteger contentStart = output.length;
|
|
44
|
+
|
|
45
|
+
[_rendererFactory renderChildrenOfNode:node into:output context:context];
|
|
46
|
+
|
|
47
|
+
NSRange contentRange = NSMakeRange(contentStart, output.length - contentStart);
|
|
48
|
+
if (contentRange.length == 0) {
|
|
49
|
+
if (hasSpacers) {
|
|
50
|
+
[output deleteCharactersInRange:NSMakeRange(start, contentStart - start)];
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
36
55
|
// 3. Apply core link functionality (non-destructive)
|
|
37
|
-
[output addAttribute:NSLinkAttributeName value:url range:
|
|
56
|
+
[output addAttribute:NSLinkAttributeName value:url range:contentRange];
|
|
38
57
|
|
|
39
58
|
// 4. Optimize visual attributes via enumeration to avoid redundant updates
|
|
40
|
-
[output enumerateAttributesInRange:
|
|
59
|
+
[output enumerateAttributesInRange:contentRange
|
|
41
60
|
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
|
|
42
61
|
usingBlock:^(NSDictionary<NSAttributedStringKey, id> *attrs, NSRange subrange, BOOL *stop) {
|
|
43
62
|
NSMutableDictionary *newAttributes = [NSMutableDictionary dictionary];
|
|
@@ -74,10 +93,23 @@
|
|
|
74
93
|
}
|
|
75
94
|
}];
|
|
76
95
|
|
|
77
|
-
|
|
78
|
-
|
|
96
|
+
// 5. Reserve horizontal padding with a spacer at each outer edge (see
|
|
97
|
+
// ENRMLinkSpacerString). Spacers sit inside the link range, so tapping the
|
|
98
|
+
// padding hits the link.
|
|
99
|
+
NSRange range = contentRange;
|
|
100
|
+
if (hasSpacers) {
|
|
101
|
+
NSDictionary *firstAttrs = [output attributesAtIndex:contentRange.location effectiveRange:NULL];
|
|
102
|
+
NSDictionary *lastAttrs = [output attributesAtIndex:NSMaxRange(contentRange) - 1 effectiveRange:NULL];
|
|
103
|
+
NSAttributedString *leading = ENRMLinkSpacerString(firstAttrs, chip.paddingHorizontal);
|
|
104
|
+
[output setAttributes:[leading attributesAtIndex:0 effectiveRange:NULL] range:NSMakeRange(start, 1)];
|
|
105
|
+
[output appendAttributedString:ENRMLinkSpacerString(lastAttrs, chip.paddingHorizontal)];
|
|
106
|
+
range = NSMakeRange(start, output.length - start);
|
|
79
107
|
}
|
|
80
108
|
|
|
109
|
+
// 6. Chip background/border is drawn by the layout manager (see ENRMLinkChip).
|
|
110
|
+
// Attached even when invisible so the chip's padding is known to link-layout reporting.
|
|
111
|
+
[output addAttribute:ENRMLinkChipAttributeName value:chip range:range];
|
|
112
|
+
|
|
81
113
|
[context registerLinkRange:range url:url];
|
|
82
114
|
}
|
|
83
115
|
|
package/ios/styles/StyleConfig.h
CHANGED
|
@@ -13,6 +13,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
13
13
|
@property (nonatomic, strong) RCTUIColor *color;
|
|
14
14
|
@property (nonatomic, assign) BOOL underline;
|
|
15
15
|
@property (nonatomic, strong, nullable) RCTUIColor *backgroundColor;
|
|
16
|
+
// Chip ("box") properties — never inherited from the base link style.
|
|
17
|
+
@property (nonatomic, assign) CGFloat borderRadius;
|
|
18
|
+
@property (nonatomic, assign) CGFloat paddingHorizontal;
|
|
19
|
+
@property (nonatomic, assign) CGFloat paddingVertical;
|
|
20
|
+
@property (nonatomic, strong, nullable) RCTUIColor *borderColor;
|
|
21
|
+
@property (nonatomic, assign) CGFloat borderWidth;
|
|
16
22
|
@end
|
|
17
23
|
|
|
18
24
|
@interface StyleConfig : NSObject <NSCopying>
|
|
@@ -166,6 +172,16 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
166
172
|
- (void)setLinkUnderline:(BOOL)newValue;
|
|
167
173
|
- (nullable RCTUIColor *)linkBackgroundColor;
|
|
168
174
|
- (void)setLinkBackgroundColor:(nullable RCTUIColor *)newValue;
|
|
175
|
+
- (CGFloat)linkBorderRadius;
|
|
176
|
+
- (void)setLinkBorderRadius:(CGFloat)newValue;
|
|
177
|
+
- (CGFloat)linkPaddingHorizontal;
|
|
178
|
+
- (void)setLinkPaddingHorizontal:(CGFloat)newValue;
|
|
179
|
+
- (CGFloat)linkPaddingVertical;
|
|
180
|
+
- (void)setLinkPaddingVertical:(CGFloat)newValue;
|
|
181
|
+
- (nullable RCTUIColor *)linkBorderColor;
|
|
182
|
+
- (void)setLinkBorderColor:(nullable RCTUIColor *)newValue;
|
|
183
|
+
- (CGFloat)linkBorderWidth;
|
|
184
|
+
- (void)setLinkBorderWidth:(CGFloat)newValue;
|
|
169
185
|
- (NSArray<LinkVariantConfig *> *)linkVariants;
|
|
170
186
|
- (void)setLinkVariants:(NSArray<LinkVariantConfig *> *)newValue;
|
|
171
187
|
- (nullable LinkVariantConfig *)effectiveLinkVariantForURL:(NSString *)url;
|
|
@@ -113,6 +113,11 @@ static inline NSString *normalizedFontWeight(NSString *fontWeight)
|
|
|
113
113
|
RCTUIColor *_linkColor;
|
|
114
114
|
BOOL _linkUnderline;
|
|
115
115
|
RCTUIColor *_linkBackgroundColor;
|
|
116
|
+
CGFloat _linkBorderRadius;
|
|
117
|
+
CGFloat _linkPaddingHorizontal;
|
|
118
|
+
CGFloat _linkPaddingVertical;
|
|
119
|
+
RCTUIColor *_linkBorderColor;
|
|
120
|
+
CGFloat _linkBorderWidth;
|
|
116
121
|
NSArray<LinkVariantConfig *> *_linkVariants;
|
|
117
122
|
NSArray<LinkVariantRegexEntry *> *_compiledVariantRegexes;
|
|
118
123
|
// Strong properties
|
|
@@ -405,6 +410,11 @@ static inline NSString *normalizedFontWeight(NSString *fontWeight)
|
|
|
405
410
|
copy->_linkColor = [_linkColor copy];
|
|
406
411
|
copy->_linkUnderline = _linkUnderline;
|
|
407
412
|
copy->_linkBackgroundColor = [_linkBackgroundColor copy];
|
|
413
|
+
copy->_linkBorderRadius = _linkBorderRadius;
|
|
414
|
+
copy->_linkPaddingHorizontal = _linkPaddingHorizontal;
|
|
415
|
+
copy->_linkPaddingVertical = _linkPaddingVertical;
|
|
416
|
+
copy->_linkBorderColor = [_linkBorderColor copy];
|
|
417
|
+
copy->_linkBorderWidth = _linkBorderWidth;
|
|
408
418
|
copy->_linkVariants = [_linkVariants copy];
|
|
409
419
|
copy->_compiledVariantRegexes = [_compiledVariantRegexes copy];
|
|
410
420
|
copy->_strongFontFamily = [_strongFontFamily copy];
|
|
@@ -1334,6 +1344,56 @@ static inline NSString *normalizedFontWeight(NSString *fontWeight)
|
|
|
1334
1344
|
_linkBackgroundColor = newValue;
|
|
1335
1345
|
}
|
|
1336
1346
|
|
|
1347
|
+
- (CGFloat)linkBorderRadius
|
|
1348
|
+
{
|
|
1349
|
+
return _linkBorderRadius;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
- (void)setLinkBorderRadius:(CGFloat)newValue
|
|
1353
|
+
{
|
|
1354
|
+
_linkBorderRadius = newValue;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
- (CGFloat)linkPaddingHorizontal
|
|
1358
|
+
{
|
|
1359
|
+
return _linkPaddingHorizontal;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
- (void)setLinkPaddingHorizontal:(CGFloat)newValue
|
|
1363
|
+
{
|
|
1364
|
+
_linkPaddingHorizontal = newValue;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
- (CGFloat)linkPaddingVertical
|
|
1368
|
+
{
|
|
1369
|
+
return _linkPaddingVertical;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
- (void)setLinkPaddingVertical:(CGFloat)newValue
|
|
1373
|
+
{
|
|
1374
|
+
_linkPaddingVertical = newValue;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
- (RCTUIColor *)linkBorderColor
|
|
1378
|
+
{
|
|
1379
|
+
return _linkBorderColor;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
- (void)setLinkBorderColor:(RCTUIColor *)newValue
|
|
1383
|
+
{
|
|
1384
|
+
_linkBorderColor = newValue;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
- (CGFloat)linkBorderWidth
|
|
1388
|
+
{
|
|
1389
|
+
return _linkBorderWidth;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
- (void)setLinkBorderWidth:(CGFloat)newValue
|
|
1393
|
+
{
|
|
1394
|
+
_linkBorderWidth = newValue;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1337
1397
|
- (NSArray<LinkVariantConfig *> *)linkVariants
|
|
1338
1398
|
{
|
|
1339
1399
|
return _linkVariants;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#import "CodeBackground.h"
|
|
2
|
+
#import "ENRMChipDrawing.h"
|
|
2
3
|
#import "ENRMUIKit.h"
|
|
3
4
|
#import "RenderContext.h"
|
|
4
5
|
|
|
@@ -116,71 +117,8 @@ static const CGFloat kCodeBackgroundBorderWidth = 0.5;
|
|
|
116
117
|
isFirst:(BOOL)isFirst
|
|
117
118
|
isLast:(BOOL)isLast
|
|
118
119
|
{
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
[path fill];
|
|
122
|
-
|
|
123
|
-
if (borderColor) {
|
|
124
|
-
[borderColor setStroke];
|
|
125
|
-
UIBezierPath *strokePath =
|
|
126
|
-
(isFirst && isLast) ? path : [self openBorderPathForRect:rect isFirst:isFirst isLast:isLast];
|
|
127
|
-
strokePath.lineWidth = kCodeBackgroundBorderWidth;
|
|
128
|
-
BezierPathSetRoundStyle(strokePath);
|
|
129
|
-
[strokePath stroke];
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
- (UIBezierPath *)pathForRect:(CGRect)rect isFirst:(BOOL)isFirst isLast:(BOOL)isLast
|
|
134
|
-
{
|
|
135
|
-
#if !TARGET_OS_OSX
|
|
136
|
-
UIRectCorner corners = 0;
|
|
137
|
-
if (isFirst)
|
|
138
|
-
corners |= (UIRectCornerTopLeft | UIRectCornerBottomLeft);
|
|
139
|
-
if (isLast)
|
|
140
|
-
corners |= (UIRectCornerTopRight | UIRectCornerBottomRight);
|
|
141
|
-
|
|
142
|
-
return [UIBezierPath bezierPathWithRoundedRect:rect
|
|
143
|
-
byRoundingCorners:corners
|
|
144
|
-
cornerRadii:CGSizeMake(kCodeBackgroundCornerRadius, kCodeBackgroundCornerRadius)];
|
|
145
|
-
#else
|
|
146
|
-
// NSBezierPath doesn't support per-corner rounding — use uniform corner radius
|
|
147
|
-
CGFloat cornerRadius = (isFirst || isLast) ? kCodeBackgroundCornerRadius : 0;
|
|
148
|
-
return UIBezierPathWithRoundedRect(rect, cornerRadius);
|
|
149
|
-
#endif
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
- (UIBezierPath *)openBorderPathForRect:(CGRect)rect isFirst:(BOOL)isFirst isLast:(BOOL)isLast
|
|
153
|
-
{
|
|
154
|
-
UIBezierPath *path = [UIBezierPath bezierPath];
|
|
155
|
-
CGFloat r = kCodeBackgroundCornerRadius;
|
|
156
|
-
CGFloat inset = kCodeBackgroundBorderWidth / 2.0;
|
|
157
|
-
CGRect insetRect = CGRectInset(rect, inset, inset);
|
|
158
|
-
|
|
159
|
-
if (isFirst) {
|
|
160
|
-
[path moveToPoint:CGPointMake(CGRectGetMaxX(rect), insetRect.origin.y)];
|
|
161
|
-
BezierPathAddLine(path, CGPointMake(insetRect.origin.x + r, insetRect.origin.y));
|
|
162
|
-
BezierPathAddQuadCurve(path, CGPointMake(insetRect.origin.x, insetRect.origin.y + r),
|
|
163
|
-
CGPointMake(insetRect.origin.x, insetRect.origin.y));
|
|
164
|
-
BezierPathAddLine(path, CGPointMake(insetRect.origin.x, CGRectGetMaxY(insetRect) - r));
|
|
165
|
-
BezierPathAddQuadCurve(path, CGPointMake(insetRect.origin.x + r, CGRectGetMaxY(insetRect)),
|
|
166
|
-
CGPointMake(insetRect.origin.x, CGRectGetMaxY(insetRect)));
|
|
167
|
-
BezierPathAddLine(path, CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(insetRect)));
|
|
168
|
-
} else if (isLast) {
|
|
169
|
-
[path moveToPoint:CGPointMake(rect.origin.x, insetRect.origin.y)];
|
|
170
|
-
BezierPathAddLine(path, CGPointMake(CGRectGetMaxX(insetRect) - r, insetRect.origin.y));
|
|
171
|
-
BezierPathAddQuadCurve(path, CGPointMake(CGRectGetMaxX(insetRect), insetRect.origin.y + r),
|
|
172
|
-
CGPointMake(CGRectGetMaxX(insetRect), insetRect.origin.y));
|
|
173
|
-
BezierPathAddLine(path, CGPointMake(CGRectGetMaxX(insetRect), CGRectGetMaxY(insetRect) - r));
|
|
174
|
-
BezierPathAddQuadCurve(path, CGPointMake(CGRectGetMaxX(insetRect) - r, CGRectGetMaxY(insetRect)),
|
|
175
|
-
CGPointMake(CGRectGetMaxX(insetRect), CGRectGetMaxY(insetRect)));
|
|
176
|
-
BezierPathAddLine(path, CGPointMake(rect.origin.x, CGRectGetMaxY(insetRect)));
|
|
177
|
-
} else {
|
|
178
|
-
[path moveToPoint:CGPointMake(rect.origin.x, insetRect.origin.y)];
|
|
179
|
-
BezierPathAddLine(path, CGPointMake(CGRectGetMaxX(rect), insetRect.origin.y));
|
|
180
|
-
[path moveToPoint:CGPointMake(rect.origin.x, CGRectGetMaxY(insetRect))];
|
|
181
|
-
BezierPathAddLine(path, CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(insetRect)));
|
|
182
|
-
}
|
|
183
|
-
return path;
|
|
120
|
+
ENRMDrawChipFragment(rect, backgroundColor, borderColor, kCodeBackgroundBorderWidth, kCodeBackgroundCornerRadius,
|
|
121
|
+
isFirst, isLast, NO);
|
|
184
122
|
}
|
|
185
123
|
|
|
186
124
|
#pragma mark - Helpers
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#import "ENRMUIKit.h"
|
|
3
|
+
|
|
4
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
5
|
+
|
|
6
|
+
#ifdef __cplusplus
|
|
7
|
+
extern "C" {
|
|
8
|
+
#endif
|
|
9
|
+
|
|
10
|
+
/// Fills (and optionally strokes) one line fragment of an inline "chip" — the
|
|
11
|
+
/// rounded background shared by inline code and link chips. For a chip that
|
|
12
|
+
/// wraps, only the outer ends are rounded (`isFirst` / `isLast`) and the border
|
|
13
|
+
/// stays open where the fragment is cut.
|
|
14
|
+
///
|
|
15
|
+
/// `strokeInset` keeps a closed border inside `rect` (link chips); otherwise it
|
|
16
|
+
/// is centered on the edge (inline code, kept for pixel parity).
|
|
17
|
+
void ENRMDrawChipFragment(CGRect rect, RCTUIColor *_Nullable backgroundColor, RCTUIColor *_Nullable borderColor,
|
|
18
|
+
CGFloat borderWidth, CGFloat cornerRadius, BOOL isFirst, BOOL isLast, BOOL strokeInset);
|
|
19
|
+
|
|
20
|
+
#ifdef __cplusplus
|
|
21
|
+
}
|
|
22
|
+
#endif
|
|
23
|
+
|
|
24
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#import "ENRMChipDrawing.h"
|
|
2
|
+
|
|
3
|
+
static UIBezierPath *chipFillPath(CGRect rect, CGFloat r, BOOL isFirst, BOOL isLast)
|
|
4
|
+
{
|
|
5
|
+
#if !TARGET_OS_OSX
|
|
6
|
+
UIRectCorner corners = 0;
|
|
7
|
+
if (isFirst)
|
|
8
|
+
corners |= (UIRectCornerTopLeft | UIRectCornerBottomLeft);
|
|
9
|
+
if (isLast)
|
|
10
|
+
corners |= (UIRectCornerTopRight | UIRectCornerBottomRight);
|
|
11
|
+
|
|
12
|
+
return [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:CGSizeMake(r, r)];
|
|
13
|
+
#else
|
|
14
|
+
// NSBezierPath doesn't support per-corner rounding — use uniform corner radius
|
|
15
|
+
return UIBezierPathWithRoundedRect(rect, (isFirst || isLast) ? r : 0);
|
|
16
|
+
#endif
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static UIBezierPath *chipOpenBorderPath(CGRect rect, CGFloat r, CGFloat borderWidth, BOOL isFirst, BOOL isLast)
|
|
20
|
+
{
|
|
21
|
+
UIBezierPath *path = [UIBezierPath bezierPath];
|
|
22
|
+
CGFloat inset = borderWidth / 2.0;
|
|
23
|
+
CGRect insetRect = CGRectInset(rect, inset, inset);
|
|
24
|
+
|
|
25
|
+
if (isFirst) {
|
|
26
|
+
[path moveToPoint:CGPointMake(CGRectGetMaxX(rect), insetRect.origin.y)];
|
|
27
|
+
BezierPathAddLine(path, CGPointMake(insetRect.origin.x + r, insetRect.origin.y));
|
|
28
|
+
BezierPathAddQuadCurve(path, CGPointMake(insetRect.origin.x, insetRect.origin.y + r),
|
|
29
|
+
CGPointMake(insetRect.origin.x, insetRect.origin.y));
|
|
30
|
+
BezierPathAddLine(path, CGPointMake(insetRect.origin.x, CGRectGetMaxY(insetRect) - r));
|
|
31
|
+
BezierPathAddQuadCurve(path, CGPointMake(insetRect.origin.x + r, CGRectGetMaxY(insetRect)),
|
|
32
|
+
CGPointMake(insetRect.origin.x, CGRectGetMaxY(insetRect)));
|
|
33
|
+
BezierPathAddLine(path, CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(insetRect)));
|
|
34
|
+
} else if (isLast) {
|
|
35
|
+
[path moveToPoint:CGPointMake(rect.origin.x, insetRect.origin.y)];
|
|
36
|
+
BezierPathAddLine(path, CGPointMake(CGRectGetMaxX(insetRect) - r, insetRect.origin.y));
|
|
37
|
+
BezierPathAddQuadCurve(path, CGPointMake(CGRectGetMaxX(insetRect), insetRect.origin.y + r),
|
|
38
|
+
CGPointMake(CGRectGetMaxX(insetRect), insetRect.origin.y));
|
|
39
|
+
BezierPathAddLine(path, CGPointMake(CGRectGetMaxX(insetRect), CGRectGetMaxY(insetRect) - r));
|
|
40
|
+
BezierPathAddQuadCurve(path, CGPointMake(CGRectGetMaxX(insetRect) - r, CGRectGetMaxY(insetRect)),
|
|
41
|
+
CGPointMake(CGRectGetMaxX(insetRect), CGRectGetMaxY(insetRect)));
|
|
42
|
+
BezierPathAddLine(path, CGPointMake(rect.origin.x, CGRectGetMaxY(insetRect)));
|
|
43
|
+
} else {
|
|
44
|
+
[path moveToPoint:CGPointMake(rect.origin.x, insetRect.origin.y)];
|
|
45
|
+
BezierPathAddLine(path, CGPointMake(CGRectGetMaxX(rect), insetRect.origin.y));
|
|
46
|
+
[path moveToPoint:CGPointMake(rect.origin.x, CGRectGetMaxY(insetRect))];
|
|
47
|
+
BezierPathAddLine(path, CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(insetRect)));
|
|
48
|
+
}
|
|
49
|
+
return path;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
void ENRMDrawChipFragment(CGRect rect, RCTUIColor *backgroundColor, RCTUIColor *borderColor, CGFloat borderWidth,
|
|
53
|
+
CGFloat cornerRadius, BOOL isFirst, BOOL isLast, BOOL strokeInset)
|
|
54
|
+
{
|
|
55
|
+
if (CGRectIsEmpty(rect))
|
|
56
|
+
return;
|
|
57
|
+
|
|
58
|
+
CGFloat r = MIN(MAX(cornerRadius, 0), MIN(rect.size.width, rect.size.height) / 2.0);
|
|
59
|
+
|
|
60
|
+
if (backgroundColor) {
|
|
61
|
+
[backgroundColor setFill];
|
|
62
|
+
[chipFillPath(rect, r, isFirst, isLast) fill];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!borderColor || borderWidth <= 0)
|
|
66
|
+
return;
|
|
67
|
+
|
|
68
|
+
[borderColor setStroke];
|
|
69
|
+
UIBezierPath *strokePath;
|
|
70
|
+
if (isFirst && isLast) {
|
|
71
|
+
if (strokeInset) {
|
|
72
|
+
CGFloat inset = borderWidth / 2.0;
|
|
73
|
+
strokePath = chipFillPath(CGRectInset(rect, inset, inset), MAX(r - inset, 0), YES, YES);
|
|
74
|
+
} else {
|
|
75
|
+
strokePath = chipFillPath(rect, r, YES, YES);
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
strokePath = chipOpenBorderPath(rect, r, borderWidth, isFirst, isLast);
|
|
79
|
+
}
|
|
80
|
+
strokePath.lineWidth = borderWidth;
|
|
81
|
+
BezierPathSetRoundStyle(strokePath);
|
|
82
|
+
[strokePath stroke];
|
|
83
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#import "ENRMUIKit.h"
|
|
3
|
+
|
|
4
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
5
|
+
|
|
6
|
+
/// Marks a link range that carries chip styling. The value is one
|
|
7
|
+
/// `ENRMLinkChip` instance per link: instances compare by identity, so two
|
|
8
|
+
/// adjacent links never coalesce into one attribute run (one chip).
|
|
9
|
+
extern NSString *const ENRMLinkChipAttributeName;
|
|
10
|
+
|
|
11
|
+
/// Marks the spacer characters `LinkRenderer` inserts at both outer edges of a
|
|
12
|
+
/// link to reserve `paddingHorizontal` in the line. Spacers are layout-only:
|
|
13
|
+
/// every text exit point (copy, markdown/HTML export, decoration text) strips
|
|
14
|
+
/// them by this attribute — never by character, since U+00A0 can be content.
|
|
15
|
+
extern NSString *const ENRMLinkSpacerAttributeName;
|
|
16
|
+
|
|
17
|
+
@interface ENRMLinkChip : NSObject
|
|
18
|
+
@property (nonatomic, strong, nullable) RCTUIColor *backgroundColor;
|
|
19
|
+
@property (nonatomic, strong, nullable) RCTUIColor *borderColor;
|
|
20
|
+
@property (nonatomic, assign) CGFloat borderWidth;
|
|
21
|
+
@property (nonatomic, assign) CGFloat borderRadius;
|
|
22
|
+
@property (nonatomic, assign) CGFloat paddingHorizontal;
|
|
23
|
+
@property (nonatomic, assign) CGFloat paddingVertical;
|
|
24
|
+
/// YES when the chip paints something (a background or a visible border).
|
|
25
|
+
@property (nonatomic, readonly) BOOL isVisible;
|
|
26
|
+
@end
|
|
27
|
+
|
|
28
|
+
#ifdef __cplusplus
|
|
29
|
+
extern "C" {
|
|
30
|
+
#endif
|
|
31
|
+
|
|
32
|
+
/// A spacer character (U+00A0) whose advance is exactly `width`.
|
|
33
|
+
///
|
|
34
|
+
/// U+00A0 is line-break class GL: a break is allowed before it after a space
|
|
35
|
+
/// but never after it, and never before it after a non-space — so a leading
|
|
36
|
+
/// spacer always travels with the link's first glyph and a trailing one with
|
|
37
|
+
/// its last glyph (no orphaned padding at a wrap). It uses a 1pt system font
|
|
38
|
+
/// so its own advance and line-height contribution are negligible whatever
|
|
39
|
+
/// the link font (a custom font may lack U+00A0), and NSKern makes up the rest.
|
|
40
|
+
NSAttributedString *ENRMLinkSpacerString(NSDictionary<NSAttributedStringKey, id> *baseAttributes, CGFloat width);
|
|
41
|
+
|
|
42
|
+
/// `string` without link spacers (returns `string` itself when it has none).
|
|
43
|
+
NSAttributedString *ENRMAttributedStringByRemovingLinkSpacers(NSAttributedString *string);
|
|
44
|
+
|
|
45
|
+
/// Plain text of `range` without link spacers.
|
|
46
|
+
NSString *ENRMPlainTextByRemovingLinkSpacers(NSAttributedString *string, NSRange range);
|
|
47
|
+
|
|
48
|
+
/// Falls back to a plain `NSBackgroundColorAttributeName` for every chip
|
|
49
|
+
/// background, for text drawn without `TextViewLayoutManager` (table cells,
|
|
50
|
+
/// RTF export). Radius, border and vertical padding are not represented.
|
|
51
|
+
void ENRMApplyLinkChipBackgroundsAsTextBackground(NSMutableAttributedString *string);
|
|
52
|
+
|
|
53
|
+
/// Enumerates the chip rect of every laid-out line fragment of the link at
|
|
54
|
+
/// `charRange`, in text-container coordinates. The rect spans the glyphs
|
|
55
|
+
/// (spacers included, so it already covers `paddingHorizontal`) horizontally
|
|
56
|
+
/// and the link font's ascender..descender ± `paddingVertical` vertically.
|
|
57
|
+
void ENRMEnumerateLinkChipFragments(NSLayoutManager *layoutManager, NSTextContainer *textContainer, NSRange charRange,
|
|
58
|
+
ENRMLinkChip *_Nullable chip,
|
|
59
|
+
void (^block)(CGRect rect, BOOL isFirst, BOOL isLast));
|
|
60
|
+
|
|
61
|
+
/// Draws the visible link chips intersecting `glyphsToShow`. Called from the
|
|
62
|
+
/// layout manager's background pass, before glyphs are drawn.
|
|
63
|
+
void ENRMDrawLinkChipsForGlyphRange(NSLayoutManager *layoutManager, NSTextContainer *textContainer,
|
|
64
|
+
NSRange glyphsToShow, CGPoint origin);
|
|
65
|
+
|
|
66
|
+
#ifdef __cplusplus
|
|
67
|
+
}
|
|
68
|
+
#endif
|
|
69
|
+
|
|
70
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#import "ENRMLinkChip.h"
|
|
2
|
+
#import "ENRMChipDrawing.h"
|
|
3
|
+
#import <CoreText/CoreText.h>
|
|
4
|
+
|
|
5
|
+
NSString *const ENRMLinkChipAttributeName = @"ENRMLinkChip";
|
|
6
|
+
NSString *const ENRMLinkSpacerAttributeName = @"ENRMLinkSpacer";
|
|
7
|
+
|
|
8
|
+
static const unichar kSpacerChar = 0x00A0;
|
|
9
|
+
static const CGFloat kSpacerFontSize = 1.0;
|
|
10
|
+
|
|
11
|
+
@implementation ENRMLinkChip
|
|
12
|
+
|
|
13
|
+
- (BOOL)isVisible
|
|
14
|
+
{
|
|
15
|
+
return self.backgroundColor != nil || (self.borderColor != nil && self.borderWidth > 0);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@end
|
|
19
|
+
|
|
20
|
+
#pragma mark - Spacers
|
|
21
|
+
|
|
22
|
+
static CGFloat spacerAdvance(UIFont *font)
|
|
23
|
+
{
|
|
24
|
+
CTFontRef ctFont = (__bridge CTFontRef)font;
|
|
25
|
+
UniChar character = kSpacerChar;
|
|
26
|
+
CGGlyph glyph = 0;
|
|
27
|
+
if (!CTFontGetGlyphsForCharacters(ctFont, &character, &glyph, 1))
|
|
28
|
+
return 0;
|
|
29
|
+
CGSize advance = CGSizeZero;
|
|
30
|
+
CTFontGetAdvancesForGlyphs(ctFont, kCTFontOrientationHorizontal, &glyph, &advance, 1);
|
|
31
|
+
return advance.width;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
NSAttributedString *ENRMLinkSpacerString(NSDictionary<NSAttributedStringKey, id> *baseAttributes, CGFloat width)
|
|
35
|
+
{
|
|
36
|
+
static UIFont *spacerFont;
|
|
37
|
+
static CGFloat spacerFontAdvance;
|
|
38
|
+
static dispatch_once_t onceToken;
|
|
39
|
+
dispatch_once(&onceToken, ^{
|
|
40
|
+
spacerFont = [UIFont systemFontOfSize:kSpacerFontSize];
|
|
41
|
+
spacerFontAdvance = spacerAdvance(spacerFont);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
NSMutableDictionary *attributes = [baseAttributes mutableCopy] ?: [NSMutableDictionary dictionary];
|
|
45
|
+
attributes[NSFontAttributeName] = spacerFont;
|
|
46
|
+
attributes[NSKernAttributeName] = @(width - spacerFontAdvance);
|
|
47
|
+
attributes[ENRMLinkSpacerAttributeName] = @YES;
|
|
48
|
+
// Padding is not text: keep decorations and attachments off it.
|
|
49
|
+
[attributes removeObjectForKey:NSUnderlineStyleAttributeName];
|
|
50
|
+
[attributes removeObjectForKey:NSStrikethroughStyleAttributeName];
|
|
51
|
+
[attributes removeObjectForKey:NSAttachmentAttributeName];
|
|
52
|
+
|
|
53
|
+
NSString *spacer = [NSString stringWithCharacters:&kSpacerChar length:1];
|
|
54
|
+
return [[NSAttributedString alloc] initWithString:spacer attributes:attributes];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
NSAttributedString *ENRMAttributedStringByRemovingLinkSpacers(NSAttributedString *string)
|
|
58
|
+
{
|
|
59
|
+
__block NSMutableAttributedString *stripped = nil;
|
|
60
|
+
[string enumerateAttribute:ENRMLinkSpacerAttributeName
|
|
61
|
+
inRange:NSMakeRange(0, string.length)
|
|
62
|
+
options:NSAttributedStringEnumerationReverse
|
|
63
|
+
usingBlock:^(id value, NSRange range, BOOL *stop) {
|
|
64
|
+
if (!value)
|
|
65
|
+
return;
|
|
66
|
+
if (!stripped)
|
|
67
|
+
stripped = [string mutableCopy];
|
|
68
|
+
[stripped deleteCharactersInRange:range];
|
|
69
|
+
}];
|
|
70
|
+
return stripped ?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
NSString *ENRMPlainTextByRemovingLinkSpacers(NSAttributedString *string, NSRange range)
|
|
74
|
+
{
|
|
75
|
+
if (range.location == NSNotFound || NSMaxRange(range) > string.length)
|
|
76
|
+
return @"";
|
|
77
|
+
return ENRMAttributedStringByRemovingLinkSpacers([string attributedSubstringFromRange:range]).string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
void ENRMApplyLinkChipBackgroundsAsTextBackground(NSMutableAttributedString *string)
|
|
81
|
+
{
|
|
82
|
+
[string enumerateAttribute:ENRMLinkChipAttributeName
|
|
83
|
+
inRange:NSMakeRange(0, string.length)
|
|
84
|
+
options:0
|
|
85
|
+
usingBlock:^(ENRMLinkChip *chip, NSRange range, BOOL *stop) {
|
|
86
|
+
if (chip.backgroundColor) {
|
|
87
|
+
[string addAttribute:NSBackgroundColorAttributeName value:chip.backgroundColor range:range];
|
|
88
|
+
}
|
|
89
|
+
}];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
#pragma mark - Geometry
|
|
93
|
+
|
|
94
|
+
/// The font of the first non-spacer character of `charRange` — the spacer's
|
|
95
|
+
/// 1pt font must not drive the chip's height.
|
|
96
|
+
static UIFont *_Nullable chipFontInRange(NSTextStorage *storage, NSRange charRange)
|
|
97
|
+
{
|
|
98
|
+
__block UIFont *font = nil;
|
|
99
|
+
[storage enumerateAttributesInRange:charRange
|
|
100
|
+
options:0
|
|
101
|
+
usingBlock:^(NSDictionary<NSAttributedStringKey, id> *attrs, NSRange range, BOOL *stop) {
|
|
102
|
+
if (attrs[ENRMLinkSpacerAttributeName])
|
|
103
|
+
return;
|
|
104
|
+
font = attrs[NSFontAttributeName];
|
|
105
|
+
*stop = font != nil;
|
|
106
|
+
}];
|
|
107
|
+
return font;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
void ENRMEnumerateLinkChipFragments(NSLayoutManager *layoutManager, NSTextContainer *textContainer, NSRange charRange,
|
|
111
|
+
ENRMLinkChip *chip, void (^block)(CGRect rect, BOOL isFirst, BOOL isLast))
|
|
112
|
+
{
|
|
113
|
+
NSTextStorage *storage = layoutManager.textStorage;
|
|
114
|
+
if (charRange.length == 0 || NSMaxRange(charRange) > storage.length)
|
|
115
|
+
return;
|
|
116
|
+
|
|
117
|
+
NSRange glyphRange = [layoutManager glyphRangeForCharacterRange:charRange actualCharacterRange:NULL];
|
|
118
|
+
if (glyphRange.location == NSNotFound || glyphRange.length == 0)
|
|
119
|
+
return;
|
|
120
|
+
|
|
121
|
+
CGFloat paddingHorizontal = chip.paddingHorizontal;
|
|
122
|
+
CGFloat paddingVertical = chip.paddingVertical;
|
|
123
|
+
|
|
124
|
+
[layoutManager
|
|
125
|
+
enumerateLineFragmentsForGlyphRange:glyphRange
|
|
126
|
+
usingBlock:^(CGRect lineRect, CGRect usedRect, NSTextContainer *tc, NSRange lineRange,
|
|
127
|
+
BOOL *stop) {
|
|
128
|
+
NSRange intersect = NSIntersectionRange(lineRange, glyphRange);
|
|
129
|
+
if (intersect.length == 0)
|
|
130
|
+
return;
|
|
131
|
+
|
|
132
|
+
BOOL isFirst = intersect.location == glyphRange.location;
|
|
133
|
+
BOOL isLast = NSMaxRange(intersect) == NSMaxRange(glyphRange);
|
|
134
|
+
|
|
135
|
+
CGRect textRect = [layoutManager boundingRectForGlyphRange:intersect
|
|
136
|
+
inTextContainer:textContainer];
|
|
137
|
+
CGFloat left = CGRectGetMinX(textRect);
|
|
138
|
+
CGFloat right = CGRectGetMaxX(textRect);
|
|
139
|
+
|
|
140
|
+
// The trailing spacer's kern is its advance; don't rely on the
|
|
141
|
+
// glyph bounds including it.
|
|
142
|
+
if (isLast && paddingHorizontal > 0) {
|
|
143
|
+
NSUInteger lastGlyph = NSMaxRange(intersect) - 1;
|
|
144
|
+
CGFloat spacerX =
|
|
145
|
+
lineRect.origin.x + [layoutManager locationForGlyphAtIndex:lastGlyph].x;
|
|
146
|
+
right = MAX(right, spacerX + paddingHorizontal);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
NSRange fragmentChars = [layoutManager characterRangeForGlyphRange:intersect
|
|
150
|
+
actualGlyphRange:NULL];
|
|
151
|
+
UIFont *font = chipFontInRange(storage, fragmentChars);
|
|
152
|
+
CGFloat top = CGRectGetMinY(textRect);
|
|
153
|
+
CGFloat bottom = CGRectGetMaxY(textRect);
|
|
154
|
+
if (font) {
|
|
155
|
+
// locationForGlyphAtIndex: y is the baseline within the line fragment.
|
|
156
|
+
CGFloat baseline =
|
|
157
|
+
lineRect.origin.y + [layoutManager locationForGlyphAtIndex:intersect.location].y;
|
|
158
|
+
top = baseline - font.ascender;
|
|
159
|
+
bottom = baseline - font.descender;
|
|
160
|
+
}
|
|
161
|
+
top -= paddingVertical;
|
|
162
|
+
bottom += paddingVertical;
|
|
163
|
+
|
|
164
|
+
block(CGRectMake(left, top, right - left, bottom - top), isFirst, isLast);
|
|
165
|
+
}];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
#pragma mark - Drawing
|
|
169
|
+
|
|
170
|
+
void ENRMDrawLinkChipsForGlyphRange(NSLayoutManager *layoutManager, NSTextContainer *textContainer,
|
|
171
|
+
NSRange glyphsToShow, CGPoint origin)
|
|
172
|
+
{
|
|
173
|
+
NSTextStorage *storage = layoutManager.textStorage;
|
|
174
|
+
NSRange charRange = [layoutManager characterRangeForGlyphRange:glyphsToShow actualGlyphRange:NULL];
|
|
175
|
+
if (charRange.location == NSNotFound || charRange.length == 0)
|
|
176
|
+
return;
|
|
177
|
+
|
|
178
|
+
// Enumerate the whole storage (like CodeBackground) so a chip that starts
|
|
179
|
+
// before the dirty range still gets its full rect.
|
|
180
|
+
[storage
|
|
181
|
+
enumerateAttribute:ENRMLinkChipAttributeName
|
|
182
|
+
inRange:NSMakeRange(0, storage.length)
|
|
183
|
+
options:0
|
|
184
|
+
usingBlock:^(ENRMLinkChip *chip, NSRange range, BOOL *stop) {
|
|
185
|
+
if (!chip.isVisible || NSIntersectionRange(range, charRange).length == 0)
|
|
186
|
+
return;
|
|
187
|
+
|
|
188
|
+
ENRMEnumerateLinkChipFragments(
|
|
189
|
+
layoutManager, textContainer, range, chip, ^(CGRect rect, BOOL isFirst, BOOL isLast) {
|
|
190
|
+
ENRMDrawChipFragment(CGRectOffset(rect, origin.x, origin.y), chip.backgroundColor,
|
|
191
|
+
chip.borderColor, chip.borderWidth, chip.borderRadius, isFirst, isLast, YES);
|
|
192
|
+
});
|
|
193
|
+
}];
|
|
194
|
+
}
|