diff --git a/Examples/UIExplorer/TextExample.ios.js b/Examples/UIExplorer/TextExample.ios.js index 4889bc472..10323c7a5 100644 --- a/Examples/UIExplorer/TextExample.ios.js +++ b/Examples/UIExplorer/TextExample.ios.js @@ -218,6 +218,26 @@ exports.examples = [ ); }, +}, { + title: 'Letter Spacing', + render: function() { + return ( + + + letterSpacing = 0 + + + letterSpacing = 2 + + + letterSpacing = 9 + + + letterSpacing = -1 + + + ); + }, }, { title: 'Spaces', render: function() { diff --git a/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTextExampleSnapshot_1@2x.png b/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTextExampleSnapshot_1@2x.png index 2c6675b7b..edde59bcc 100644 Binary files a/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTextExampleSnapshot_1@2x.png and b/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTextExampleSnapshot_1@2x.png differ diff --git a/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m b/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m index df748ef03..974e3281c 100644 --- a/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m +++ b/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m @@ -88,35 +88,19 @@ XCTAssertTrue(foundElement, @"Cound't find element with '' text in %d seconds", TIMEOUT_SECONDS); } -- (void)testViewExampleSnapshot -{ - [_runner runTest:_cmd module:@"ViewExample"]; +#define RCT_SNAPSHOT_TEST(name, reRecord) \ +- (void)test##name##Snapshot \ +{ \ + _runner.recordMode |= reRecord; \ + [_runner runTest:_cmd module:@#name]; \ } -- (void)testLayoutExampleSnapshot -{ - [_runner runTest:_cmd module:@"LayoutExample"]; -} - -- (void)testTextExampleSnapshot -{ - [_runner runTest:_cmd module:@"TextExample"]; -} - -- (void)testSwitchExampleSnapshot -{ - [_runner runTest:_cmd module:@"SwitchExample"]; -} - -- (void)testSliderExampleSnapshot -{ - [_runner runTest:_cmd module:@"SliderExample"]; -} - -- (void)testTabBarExampleSnapshot -{ - [_runner runTest:_cmd module:@"TabBarExample"]; -} +RCT_SNAPSHOT_TEST(ViewExample, NO) +RCT_SNAPSHOT_TEST(LayoutExample, NO) +RCT_SNAPSHOT_TEST(TextExample, NO) +RCT_SNAPSHOT_TEST(SwitchExample, NO) +RCT_SNAPSHOT_TEST(SliderExample, NO) +RCT_SNAPSHOT_TEST(TabBarExample, NO) // Make sure this test runs last - (void)testZZZ_NotInRecordMode diff --git a/Libraries/Text/RCTShadowText.h b/Libraries/Text/RCTShadowText.h index 286edb53a..7a7b44cf8 100644 --- a/Libraries/Text/RCTShadowText.h +++ b/Libraries/Text/RCTShadowText.h @@ -22,6 +22,7 @@ extern NSString *const RCTReactTagAttributeName; @property (nonatomic, copy) NSString *fontWeight; @property (nonatomic, copy) NSString *fontStyle; @property (nonatomic, assign) BOOL isHighlighted; +@property (nonatomic, assign) CGFloat letterSpacing; @property (nonatomic, assign) CGFloat lineHeight; @property (nonatomic, assign) NSUInteger maximumNumberOfLines; @property (nonatomic, assign) CGSize shadowOffset; @@ -30,6 +31,7 @@ extern NSString *const RCTReactTagAttributeName; // Not exposed to JS @property (nonatomic, strong) UIFont *font; @property (nonatomic, assign) NSLineBreakMode truncationMode; +@property (nonatomic, assign) CGFloat effectiveLetterSpacing; @property (nonatomic, copy, readonly) NSAttributedString *attributedString; @property (nonatomic, strong, readonly) NSLayoutManager *layoutManager; diff --git a/Libraries/Text/RCTShadowText.m b/Libraries/Text/RCTShadowText.m index 4ea9d3bd6..b7e6f997e 100644 --- a/Libraries/Text/RCTShadowText.m +++ b/Libraries/Text/RCTShadowText.m @@ -40,11 +40,15 @@ static css_dim_t RCTMeasure(void *context, float width) css_dim_t result; result.dimensions[CSS_WIDTH] = RCTCeilPixelValue(computedSize.width); + if (shadowText.effectiveLetterSpacing < 0) { + result.dimensions[CSS_WIDTH] -= shadowText.effectiveLetterSpacing; + } result.dimensions[CSS_HEIGHT] = RCTCeilPixelValue(computedSize.height); return result; } -@implementation RCTShadowText { +@implementation RCTShadowText +{ NSLayoutManager *_layoutManager; NSTextContainer *_textContainer; NSAttributedString *_cachedAttributedString; @@ -55,6 +59,7 @@ static css_dim_t RCTMeasure(void *context, float width) { if ((self = [super init])) { _fontSize = NAN; + _letterSpacing = NAN; _isHighlighted = NO; _textContainer = [[NSTextContainer alloc] init]; @@ -71,22 +76,24 @@ static css_dim_t RCTMeasure(void *context, float width) - (NSAttributedString *)attributedString { return [self _attributedStringWithFontFamily:nil - fontSize:0 + fontSize:nil fontWeight:nil - fontStyle:nil]; + fontStyle:nil + letterSpacing:nil]; } - (NSAttributedString *)_attributedStringWithFontFamily:(NSString *)fontFamily - fontSize:(CGFloat)fontSize + fontSize:(NSNumber *)fontSize fontWeight:(NSString *)fontWeight fontStyle:(NSString *)fontStyle + letterSpacing:(NSNumber *)letterSpacing { if (![self isTextDirty] && _cachedAttributedString) { return _cachedAttributedString; } if (_fontSize && !isnan(_fontSize)) { - fontSize = _fontSize; + fontSize = @(_fontSize); } if (_fontWeight) { fontWeight = _fontWeight; @@ -97,12 +104,17 @@ static css_dim_t RCTMeasure(void *context, float width) if (_fontFamily) { fontFamily = _fontFamily; } + if (!isnan(_letterSpacing)) { + letterSpacing = @(_letterSpacing); + } + + _effectiveLetterSpacing = letterSpacing.doubleValue; NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init]; for (RCTShadowView *child in [self reactSubviews]) { if ([child isKindOfClass:[RCTShadowText class]]) { RCTShadowText *shadowText = (RCTShadowText *)child; - [attributedString appendAttributedString:[shadowText _attributedStringWithFontFamily:fontFamily fontSize:fontSize fontWeight:fontWeight fontStyle:fontStyle]]; + [attributedString appendAttributedString:[shadowText _attributedStringWithFontFamily:fontFamily fontSize:fontSize fontWeight:fontWeight fontStyle:fontStyle letterSpacing:letterSpacing]]; } else if ([child isKindOfClass:[RCTShadowRawText class]]) { RCTShadowRawText *shadowRawText = (RCTShadowRawText *)child; [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:[shadowRawText text] ?: @""]]; @@ -123,8 +135,9 @@ static css_dim_t RCTMeasure(void *context, float width) [self _addAttribute:NSBackgroundColorAttributeName withValue:self.textBackgroundColor toAttributedString:attributedString]; } - _font = [RCTConvert UIFont:nil withFamily:fontFamily size:@(fontSize) weight:fontWeight style:fontStyle]; + _font = [RCTConvert UIFont:nil withFamily:fontFamily size:fontSize weight:fontWeight style:fontStyle]; [self _addAttribute:NSFontAttributeName withValue:_font toAttributedString:attributedString]; + [self _addAttribute:NSKernAttributeName withValue:letterSpacing toAttributedString:attributedString]; [self _addAttribute:RCTReactTagAttributeName withValue:self.reactTag toAttributedString:attributedString]; [self _setParagraphStyleOnAttributedString:attributedString]; @@ -143,7 +156,7 @@ static css_dim_t RCTMeasure(void *context, float width) - (void)_addAttribute:(NSString *)attribute withValue:(id)attributeValue toAttributedString:(NSMutableAttributedString *)attributedString { [attributedString enumerateAttribute:attribute inRange:NSMakeRange(0, [attributedString length]) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) { - if (!value) { + if (!value && attributeValue) { [attributedString addAttribute:attribute value:attributeValue range:range]; } }]; @@ -223,6 +236,7 @@ RCT_TEXT_PROPERTY(Color, _color, UIColor *); RCT_TEXT_PROPERTY(FontFamily, _fontFamily, NSString *); RCT_TEXT_PROPERTY(FontSize, _fontSize, CGFloat); RCT_TEXT_PROPERTY(FontWeight, _fontWeight, NSString *); +RCT_TEXT_PROPERTY(LetterSpacing, _letterSpacing, CGFloat); RCT_TEXT_PROPERTY(LineHeight, _lineHeight, CGFloat); RCT_TEXT_PROPERTY(ShadowOffset, _shadowOffset, CGSize); RCT_TEXT_PROPERTY(TextAlign, _textAlign, NSTextAlignment); diff --git a/Libraries/Text/RCTText.h b/Libraries/Text/RCTText.h index 24b98e991..fe7803eb5 100644 --- a/Libraries/Text/RCTText.h +++ b/Libraries/Text/RCTText.h @@ -14,7 +14,6 @@ @property (nonatomic, strong) NSLayoutManager *layoutManager; @property (nonatomic, strong) NSTextContainer *textContainer; @property (nonatomic, copy) NSAttributedString *attributedText; - @property (nonatomic, assign) UIEdgeInsets contentInset; @end diff --git a/Libraries/Text/RCTTextManager.m b/Libraries/Text/RCTTextManager.m index ef518d204..b2832a800 100644 --- a/Libraries/Text/RCTTextManager.m +++ b/Libraries/Text/RCTTextManager.m @@ -45,6 +45,7 @@ RCT_EXPORT_SHADOW_PROPERTY(fontSize, CGFloat) RCT_EXPORT_SHADOW_PROPERTY(fontWeight, NSString) RCT_EXPORT_SHADOW_PROPERTY(fontStyle, NSString) RCT_EXPORT_SHADOW_PROPERTY(isHighlighted, BOOL) +RCT_EXPORT_SHADOW_PROPERTY(letterSpacing, CGFloat) RCT_EXPORT_SHADOW_PROPERTY(lineHeight, CGFloat) RCT_EXPORT_SHADOW_PROPERTY(maximumNumberOfLines, NSInteger) RCT_EXPORT_SHADOW_PROPERTY(shadowOffset, CGSize) diff --git a/Libraries/Text/TextStylePropTypes.js b/Libraries/Text/TextStylePropTypes.js index 632bc9e9d..95a0f4cb8 100644 --- a/Libraries/Text/TextStylePropTypes.js +++ b/Libraries/Text/TextStylePropTypes.js @@ -32,6 +32,7 @@ var TextStylePropTypes = Object.assign(Object.create(ViewStylePropTypes), { writingDirection: ReactPropTypes.oneOf( ['auto' /*default*/, 'ltr', 'rtl'] ), + letterSpacing: ReactPropTypes.number, }); // Text doesn't support padding correctly (#4841912)