diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/CustomLineHeightSpan.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/CustomLineHeightSpan.java new file mode 100644 index 000000000..f12e1d1d0 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/CustomLineHeightSpan.java @@ -0,0 +1,54 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.views.text; + +import android.graphics.Paint; +import android.text.style.LineHeightSpan; + +/** + * We use a custom {@link LineHeightSpan}, because `lineSpacingExtra` is broken. Details here: + * https://github.com/facebook/react-native/issues/7546 + */ +public class CustomLineHeightSpan implements LineHeightSpan { + private final int mHeight; + + CustomLineHeightSpan(float height) { + this.mHeight = (int) Math.ceil(height); + } + + @Override + public void chooseHeight( + CharSequence text, + int start, + int end, + int spanstartv, + int v, + Paint.FontMetricsInt fm) { + // This is more complicated that I wanted it to be. You can find a good explanation of what the + // FontMetrics mean here: http://stackoverflow.com/questions/27631736. + // The general solution is that if there's not enough height to show the full line height, we + // will prioritize in this order: ascent, descent, bottom, top + + if (-fm.ascent > mHeight) { + // Show as much ascent as possible + fm.top = fm.ascent = -mHeight; + fm.bottom = fm.descent = 0; + } else if (-fm.ascent + fm.descent > mHeight) { + // Show all ascent, and as much descent as possible + fm.top = fm.ascent; + fm.bottom = fm.descent = mHeight + fm.ascent; + } else if (-fm.ascent + fm.bottom > mHeight) { + // Show all ascent, descent, as much bottom as possible + fm.top = fm.ascent; + fm.bottom = fm.ascent + mHeight; + } else if (-fm.top + fm.bottom > mHeight) { + // Show all ascent, descent, bottom, as much top as possible + fm.top = fm.bottom - mHeight; + } else { + // Show proportionally additional ascent and top + final int additional = mHeight - (-fm.top + fm.bottom); + fm.top -= additional; + fm.ascent -= additional; + } + } +} 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 f17ecec5d..aaa579ea9 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 @@ -172,6 +172,12 @@ public class ReactTextShadowNode extends LayoutShadowNode { textCSSNode.mTextShadowRadius, textCSSNode.mTextShadowColor))); } + if (!Float.isNaN(textCSSNode.getEffectiveLineHeight())) { + ops.add(new SetSpanOperation( + start, + end, + new CustomLineHeightSpan(textCSSNode.getEffectiveLineHeight()))); + } ops.add(new SetSpanOperation(start, end, new ReactTagSpan(textCSSNode.getReactTag()))); } } @@ -235,14 +241,6 @@ public class ReactTextShadowNode extends LayoutShadowNode { // technically, width should never be negative, but there is currently a bug in boolean unconstrainedWidth = widthMode == CSSMeasureMode.UNDEFINED || width < 0; - float effectiveLineHeight = reactCSSNode.getEffectiveLineHeight(); - float lineSpacingExtra = 0; - float lineSpacingMultiplier = 1; - if (!Float.isNaN(effectiveLineHeight)) { - lineSpacingExtra = effectiveLineHeight; - lineSpacingMultiplier = 0; - } - if (boring == null && (unconstrainedWidth || (!CSSConstants.isUndefined(desiredWidth) && desiredWidth <= width))) { @@ -253,8 +251,8 @@ public class ReactTextShadowNode extends LayoutShadowNode { textPaint, (int) Math.ceil(desiredWidth), Layout.Alignment.ALIGN_NORMAL, - lineSpacingMultiplier, - lineSpacingExtra, + 1.f, + 0.f, true); } else if (boring != null && (unconstrainedWidth || boring.width <= width)) { // Is used for single-line, boring text when the width is either unknown or bigger @@ -264,8 +262,8 @@ public class ReactTextShadowNode extends LayoutShadowNode { textPaint, boring.width, Layout.Alignment.ALIGN_NORMAL, - lineSpacingMultiplier, - lineSpacingExtra, + 1.f, + 0.f, boring, true); } else { @@ -275,8 +273,8 @@ public class ReactTextShadowNode extends LayoutShadowNode { textPaint, (int) width, Layout.Alignment.ALIGN_NORMAL, - lineSpacingMultiplier, - lineSpacingExtra, + 1.f, + 0.f, true); } @@ -588,7 +586,6 @@ public class ReactTextShadowNode extends LayoutShadowNode { UNSET, mContainsImages, getPadding(), - getEffectiveLineHeight(), getTextAlign() ); uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate); 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 285ddb3cc..20cb5d51f 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 @@ -10,7 +10,6 @@ package com.facebook.react.views.text; import android.text.Spannable; -import android.view.Gravity; import com.facebook.csslayout.Spacing; @@ -28,7 +27,6 @@ public class ReactTextUpdate { private final float mPaddingTop; private final float mPaddingRight; private final float mPaddingBottom; - private final float mLineHeight; private final int mTextAlign; public ReactTextUpdate( @@ -36,7 +34,6 @@ public class ReactTextUpdate { int jsEventCounter, boolean containsImages, Spacing padding, - float lineHeight, int textAlign) { mText = text; mJsEventCounter = jsEventCounter; @@ -45,7 +42,6 @@ public class ReactTextUpdate { mPaddingTop = padding.get(Spacing.TOP); mPaddingRight = padding.get(Spacing.END); mPaddingBottom = padding.get(Spacing.BOTTOM); - mLineHeight = lineHeight; mTextAlign = textAlign; } @@ -77,10 +73,6 @@ public class ReactTextUpdate { return mPaddingBottom; } - public float getLineHeight() { - return mLineHeight; - } - 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 f137a1d2f..55ae158bf 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 @@ -20,7 +20,6 @@ import android.view.Gravity; import android.view.ViewGroup; import android.widget.TextView; -import com.facebook.csslayout.FloatUtil; import com.facebook.react.uimanager.ReactCompoundView; import com.facebook.react.uimanager.ViewDefaults; import com.facebook.react.views.view.ReactViewBackgroundDrawable; @@ -65,16 +64,6 @@ public class ReactTextView extends TextView implements ReactCompoundView { (int) Math.ceil(update.getPaddingRight()), (int) Math.ceil(update.getPaddingBottom())); - float nextLineHeight = update.getLineHeight(); - if (!FloatUtil.floatsEqual(mLineHeight, nextLineHeight)) { - mLineHeight = nextLineHeight; - if (Float.isNaN(mLineHeight)) { // NaN will be used if property gets reset - setLineSpacing(0, 1); - } else { - setLineSpacing(mLineHeight, 0); - } - } - 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 246ce8120..cac809562 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 @@ -129,7 +129,6 @@ public class ReactTextInputShadowNode extends ReactTextShadowNode implements mJsEventCount, mContainsImages, getPadding(), - getEffectiveLineHeight(), mTextAlign ); uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate);