diff --git a/Libraries/Text/TextStylePropTypes.js b/Libraries/Text/TextStylePropTypes.js
index d92708683d981e..52db01a30e44d1 100644
--- a/Libraries/Text/TextStylePropTypes.js
+++ b/Libraries/Text/TextStylePropTypes.js
@@ -49,7 +49,16 @@ const TextStylePropTypes = {
textShadowRadius: ReactPropTypes.number,
textShadowColor: ColorPropType,
/**
- * @platform ios
+ * Increase or decrease the spacing between characters. Based on the platform specific
+ * rendering this style annotation will be rendered slightly differently on Android and iOS.
+ * Default is no letter spacing.
+ *
+ * Android: Only supported since Android 5+, older versions will will ignore this attribute.
+ * Please notice that additional space will be added *around* the characters and the space
+ * is calculated based on your font size and the font family. To left-align a text similar
+ * to iOS (or in different font sizes) you should add a Platform-specific negative layout attribute.
+ *
+ * iOS: The additional space will be added behind the character and is defined in points.
*/
letterSpacing: ReactPropTypes.number,
lineHeight: ReactPropTypes.number,
diff --git a/RNTester/js/TextExample.android.js b/RNTester/js/TextExample.android.js
index b5a61c00e240b8..8b95f7aac371ca 100644
--- a/RNTester/js/TextExample.android.js
+++ b/RNTester/js/TextExample.android.js
@@ -322,6 +322,22 @@ class TextExample extends React.Component<{}> {
Holisticly formulate inexpensive ideas before best-of-breed benefits. Continually expedite magnetic potentialities rather than client-focused interfaces.
+
+
+
+ letterSpacing = 0
+
+
+ letterSpacing = 2
+
+
+ letterSpacing = 9
+
+
+ letterSpacing = -1
+
+
+
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java
index fe71f1d2dba126..e557c61054ef1d 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java
@@ -80,6 +80,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";
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/CustomLetterSpacingSpan.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/CustomLetterSpacingSpan.java
new file mode 100644
index 00000000000000..d4d57847fc77c1
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/CustomLetterSpacingSpan.java
@@ -0,0 +1,42 @@
+/**
+ * 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.
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class CustomLetterSpacingSpan extends MetricAffectingSpan {
+
+ private final float mLetterSpacing;
+
+ public CustomLetterSpacingSpan(float letterSpacing) {
+ Assertions.assertCondition(!Float.isNaN(letterSpacing) && letterSpacing != 0);
+ mLetterSpacing = letterSpacing;
+ }
+
+ @Override
+ public void updateDrawState(TextPaint paint) {
+ paint.setLetterSpacing(mLetterSpacing);
+ }
+
+ @Override
+ public void updateMeasureState(TextPaint paint) {
+ paint.setLetterSpacing(mLetterSpacing);
+ }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java
index 4ceee69cf21d0c..bcc3276f95da23 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java
@@ -180,6 +180,14 @@ private static void buildSpannedFromTextCSSNode(
end,
new CustomLineHeightSpan(textShadowNode.getEffectiveLineHeight())));
}
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ if (textShadowNode.mLetterSpacing != 0) {
+ ops.add(new SetSpanOperation(
+ start,
+ end,
+ new CustomLetterSpacingSpan(textShadowNode.mLetterSpacing)));
+ }
+ }
ops.add(new SetSpanOperation(start, end, new ReactTagSpan(textShadowNode.getReactTag())));
}
}
@@ -329,7 +337,26 @@ private static int parseNumericFontWeight(String fontWeightString) {
100 * (fontWeightString.charAt(0) - '0') : -1;
}
+ /**
+ * {@link Android TextView#setLetterSpacing} expects the letter spacing value
+ * in 'EM' unit, where one is defined by the width of the letter 'M'.
+ * Typical values for slight expansion will be around 0.05.
+ * Negative values will tighten text. So we use NaN as not defined value.
+ *
+ * This method calculates an 'EM' approach based on the input
+ * and the calculated font size. With common fonts this calculated value
+ * renders a similar spacing and layout to iOS.
+ */
+ private static float calculateLetterSpacing(float letterSpacingInput, int fontSize) {
+ if (Float.isNaN(letterSpacingInput) || letterSpacingInput == 0) {
+ return 0;
+ } else {
+ return letterSpacingInput / fontSize * 2f;
+ }
+ }
+
private float mLineHeight = Float.NaN;
+ protected float mLetterSpacing = 0;
private boolean mIsColorSet = false;
private boolean mAllowFontScaling = true;
private int mColor;
@@ -340,6 +367,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;
@@ -454,6 +482,13 @@ public void setLineHeight(float lineHeight) {
markUpdated();
}
+ @ReactProp(name = ViewProps.LETTER_SPACING, defaultFloat = Float.NaN)
+ public void setLetterSpacing(float letterSpacing) {
+ mLetterSpacingInput = letterSpacing;
+ mLetterSpacing = calculateLetterSpacing(letterSpacing, mFontSize);
+ markUpdated();
+ }
+
@ReactProp(name = ViewProps.ALLOW_FONT_SCALING, defaultBoolean = true)
public void setAllowFontScaling(boolean allowFontScaling) {
if (allowFontScaling != mAllowFontScaling) {
@@ -491,6 +526,7 @@ public void setFontSize(float fontSize) {
: (float) Math.ceil(PixelUtil.toPixelFromDIP(fontSize));
}
mFontSize = (int) fontSize;
+ mLetterSpacing = calculateLetterSpacing(mLetterSpacingInput, mFontSize);
markUpdated();
}
@@ -658,6 +694,7 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {
getPadding(Spacing.TOP),
getPadding(Spacing.END),
getPadding(Spacing.BOTTOM),
+ mLetterSpacing,
getTextAlign(),
mTextBreakStrategy
);
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java
index 9f67aec6180dee..3ee797fa531f4f 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java
@@ -28,6 +28,7 @@ public class ReactTextUpdate {
private final float mPaddingBottom;
private final int mTextAlign;
private final int mTextBreakStrategy;
+ private final float mLetterSpacing;
/**
* @deprecated Use a non-deprecated constructor for ReactTextUpdate instead. This one remains
@@ -50,6 +51,7 @@ public ReactTextUpdate(
paddingTop,
paddingEnd,
paddingBottom,
+ 0f,
textAlign,
Layout.BREAK_STRATEGY_HIGH_QUALITY);
}
@@ -62,6 +64,7 @@ public ReactTextUpdate(
float paddingTop,
float paddingEnd,
float paddingBottom,
+ float letterSpacing,
int textAlign,
int textBreakStrategy) {
mText = text;
@@ -71,6 +74,7 @@ public ReactTextUpdate(
mPaddingTop = paddingTop;
mPaddingRight = paddingEnd;
mPaddingBottom = paddingBottom;
+ mLetterSpacing = letterSpacing;
mTextAlign = textAlign;
mTextBreakStrategy = textBreakStrategy;
}
@@ -103,6 +107,10 @@ public float getPaddingBottom() {
return mPaddingBottom;
}
+ public float getLetterSpacing() {
+ return mLetterSpacing;
+ }
+
public int getTextAlign() {
return mTextAlign;
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java
index fa8f87710b1b8f..0a1b64b089b02b 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java
@@ -65,6 +65,16 @@ public void setText(ReactTextUpdate update) {
(int) Math.floor(update.getPaddingRight()),
(int) Math.floor(update.getPaddingBottom()));
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ float nextLetterSpacing = update.getLetterSpacing();
+ if (Float.isNaN(nextLetterSpacing)) {
+ nextLetterSpacing = 0;
+ }
+ if (getLetterSpacing() != nextLetterSpacing) {
+ setLetterSpacing(nextLetterSpacing);
+ }
+ }
+
int nextTextAlign = update.getTextAlign();
if (mTextAlign != nextTextAlign) {
mTextAlign = nextTextAlign;
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java
index 2e6c5493c920ed..04227b245de041 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java
@@ -146,6 +146,7 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {
getPadding(Spacing.TOP),
getPadding(Spacing.RIGHT),
getPadding(Spacing.BOTTOM),
+ mLetterSpacing,
mTextAlign,
mTextBreakStrategy
);