Skip to content
Closed
30 changes: 30 additions & 0 deletions RNTester/js/TextExample.android.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -343,6 +343,36 @@ class TextExample extends React.Component<{}> {
Holisticly formulate inexpensive ideas before best-of-breed benefits. <Text style={{fontSize: 20}}>Continually</Text> expedite magnetic potentialities rather than client-focused interfaces.
</Text>
</RNTesterBlock>
<RNTesterBlock title="Letter Spacing">
<View>
<Text style={{letterSpacing: 0}}>
letterSpacing = 0
</Text>
<Text style={{letterSpacing: 2, marginTop: 5}}>
letterSpacing = 2
</Text>
<Text style={{letterSpacing: 9, marginTop: 5}}>
letterSpacing = 9
</Text>
<View style={{flexDirection: 'row'}}>
<Text style={{fontSize: 12, letterSpacing: 2, backgroundColor: 'fuchsia', marginTop: 5}}>
With size and background color
</Text>
</View>
<Text style={{letterSpacing: -1, marginTop: 5}}>
letterSpacing = -1
</Text>
<Text style={{letterSpacing: 3, backgroundColor: '#dddddd', marginTop: 5}}>
[letterSpacing = 3]
<Text style={{letterSpacing: 0, backgroundColor: '#bbbbbb'}}>
[Nested letterSpacing = 0]
</Text>
<Text style={{letterSpacing: 6, backgroundColor: '#eeeeee'}}>
[Nested letterSpacing = 6]
</Text>
</Text>
</View>
</RNTesterBlock>
<RNTesterBlock title="Empty Text">
<Text />
</RNTesterBlock>
Expand Down
14 changes: 14 additions & 0 deletions RNTester/js/TextExample.ios.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -513,9 +513,23 @@ exports.examples = [
<Text style={{letterSpacing: 9, marginTop: 5}}>
letterSpacing = 9
</Text>
<View style={{flexDirection: 'row'}}>
<Text style={{fontSize: 12, letterSpacing: 2, backgroundColor: 'fuchsia', marginTop: 5}}>
With size and background color
</Text>
</View>
<Text style={{letterSpacing: -1, marginTop: 5}}>
letterSpacing = -1
</Text>
<Text style={{letterSpacing: 3, backgroundColor: '#dddddd', marginTop: 5}}>
[letterSpacing = 3]
<Text style={{letterSpacing: 0, backgroundColor: '#bbbbbb'}}>
[Nested letterSpacing = 0]
</Text>
<Text style={{letterSpacing: 6, backgroundColor: '#eeeeee'}}>
[Nested letterSpacing = 6]
</Text>
</Text>
</View>
);
},
Expand Down
25 changes: 25 additions & 0 deletions RNTester/js/TextInputExample.android.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -569,6 +569,31 @@ exports.examples = [
);
}
},
{
title: 'letterSpacing',
render: function() {
return (
<View>
<TextInput
style={[styles.singleLine, {letterSpacing: 0}]}
placeholder="letterSpacing = 0"
/>
<TextInput
style={[styles.singleLine, {letterSpacing: 2}]}
placeholder="letterSpacing = 2"
/>
<TextInput
style={[styles.singleLine, {letterSpacing: 9}]}
placeholder="letterSpacing = 9"
/>
<TextInput
style={[styles.singleLine, {letterSpacing: -1}]}
placeholder="letterSpacing = -1"
/>
</View>
);
}
},
{
title: 'Passwords',
render: function() {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -90,6 +90,7 @@ public class ViewProps {
public static final String FONT_STYLE = "fontStyle";
public static final String FONT_FAMILY = "fontFamily";
public static final String LINE_HEIGHT = "lineHeight";
public static final String LETTER_SPACING = "letterSpacing";
public static final String NEEDS_OFFSCREEN_ALPHA_COMPOSITING = "needsOffscreenAlphaCompositing";
public static final String NUMBER_OF_LINES = "numberOfLines";
public static final String ELLIPSIZE_MODE = "ellipsizeMode";
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

package com.facebook.react.views.text;

import android.annotation.TargetApi;
import android.os.Build;
import android.text.TextPaint;
import android.text.style.MetricAffectingSpan;

import com.facebook.infer.annotation.Assertions;

/**
* A {@link MetricAffectingSpan} that allows to set the letter spacing
* on the selected text span.
*
* The letter spacing is specified in pixels, which are converted to
* ems at paint time; this span must therefore be applied after any
* spans affecting font size.
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class CustomLetterSpacingSpan extends MetricAffectingSpan {

private final float mLetterSpacing;

public CustomLetterSpacingSpan(float letterSpacing) {
mLetterSpacing = letterSpacing;
}

@Override
public void updateDrawState(TextPaint paint) {
apply(paint);
}

@Override
public void updateMeasureState(TextPaint paint) {
apply(paint);
}

private void apply(TextPaint paint) {
// mLetterSpacing and paint.getTextSize() are both in pixels,
// yielding an accurate em value.
if (!Float.isNaN(mLetterSpacing)) {
paint.setLetterSpacing(mLetterSpacing / paint.getTextSize());
}
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -119,6 +119,14 @@ private static void buildSpannedFromShadowNode(
new SetSpanOperation(
start, end, new BackgroundColorSpan(textShadowNode.mBackgroundColor)));
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (textShadowNode.mLetterSpacing != Float.NaN) {
ops.add(new SetSpanOperation(
start,
end,
new CustomLetterSpacingSpan(textShadowNode.mLetterSpacing)));
}
}
if (textShadowNode.mFontSize != UNSET) {
ops.add(new SetSpanOperation(start, end, new AbsoluteSizeSpan(textShadowNode.mFontSize)));
}
Expand DownExpand Up@@ -229,6 +237,7 @@ private static int parseNumericFontWeight(String fontWeightString) {
}

protected float mLineHeight = Float.NaN;
protected float mLetterSpacing = Float.NaN;
protected boolean mIsColorSet = false;
protected boolean mAllowFontScaling = true;
protected int mColor;
Expand All@@ -239,6 +248,7 @@ private static int parseNumericFontWeight(String fontWeightString) {
protected int mFontSize = UNSET;
protected float mFontSizeInput = UNSET;
protected float mLineHeightInput = UNSET;
protected float mLetterSpacingInput = Float.NaN;
protected int mTextAlign = Gravity.NO_GRAVITY;
protected int mTextBreakStrategy =
(Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? 0 : Layout.BREAK_STRATEGY_HIGH_QUALITY;
Expand DownExpand Up@@ -324,12 +334,22 @@ public void setLineHeight(float lineHeight) {
markUpdated();
}

@ReactProp(name = ViewProps.LETTER_SPACING, defaultFloat = Float.NaN)
public void setLetterSpacing(float letterSpacing) {
mLetterSpacingInput = letterSpacing;
mLetterSpacing = mAllowFontScaling
? PixelUtil.toPixelFromSP(mLetterSpacingInput)
: PixelUtil.toPixelFromDIP(mLetterSpacingInput);
markUpdated();
}

@ReactProp(name = ViewProps.ALLOW_FONT_SCALING, defaultBoolean = true)
public void setAllowFontScaling(boolean allowFontScaling) {
if (allowFontScaling != mAllowFontScaling) {
mAllowFontScaling = allowFontScaling;
setFontSize(mFontSizeInput);
setLineHeight(mLineHeightInput);
setLetterSpacing(mLetterSpacingInput);
markUpdated();
}
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,7 @@
import android.widget.EditText;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.views.text.CustomStyleSpan;
import com.facebook.react.views.text.ReactTagSpan;
Expand DownExpand Up@@ -82,6 +83,7 @@ public class ReactEditText extends EditText {
private @Nullable ScrollWatcher mScrollWatcher;
private final InternalKeyListener mKeyListener;
private boolean mDetectScrollMovement = false;
private float mLetterSpacingPt = 0;

private ReactViewBackgroundManager mReactBackgroundManager;

Expand DownExpand Up@@ -624,6 +626,29 @@ public void setBorderStyle(@Nullable String style) {
mReactBackgroundManager.setBorderStyle(style);
}

public void setLetterSpacingPt(float letterSpacingPt) {
mLetterSpacingPt = letterSpacingPt;
updateLetterSpacing();
}

@Override
public void setTextSize (float size) {
super.setTextSize(size);
updateLetterSpacing();
}

@Override
public void setTextSize (int unit, float size) {
super.setTextSize(unit, size);
updateLetterSpacing();
}

protected void updateLetterSpacing() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setLetterSpacing(PixelUtil.toPixelFromSP(mLetterSpacingPt) / getTextSize());
}
}

/**
* This class will redirect *TextChanged calls to the listeners only in the case where the text
* is changed by the user, and not explicitly set by JS.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -308,6 +308,14 @@ public void setOnScroll(final ReactEditText view, boolean onScroll) {
}
}

// Sets the letter spacing as an absolute point size.
// This extra handling, on top of what ReactBaseTextShadowNode already does, is required for the
// correct display of spacing in placeholder (hint) text.
@ReactProp(name = ViewProps.LETTER_SPACING, defaultFloat = 0)
public void setLetterSpacing(ReactEditText view, float letterSpacing) {
view.setLetterSpacingPt(letterSpacing);
}

@ReactProp(name = "placeholder")
public void setPlaceholder(ReactEditText view, @Nullable String placeholder) {
view.setHint(placeholder);
Expand Down