Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement letterSpacing on Android >= 5.0 #17398

Closed
wants to merge 10 commits into from
30 changes: 30 additions & 0 deletions RNTester/js/TextExample.android.js
Original file line number Diff line number Diff 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 number Diff line number Diff 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 number Diff line number Diff 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 number Diff line number Diff 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 number Diff line number Diff 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 number Diff line number Diff 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 Down Expand 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 Down Expand 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 number Diff line number Diff 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 Down Expand 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 Down Expand 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 number Diff line number Diff 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