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 ae623bf0e69763..5178281a1d8c7a 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 @@ -190,12 +190,17 @@ protected static Spannable fromTextCSSNode(ReactTextShadowNode textCSSNode) { } textCSSNode.mContainsImages = false; + textCSSNode.mHeightOfTallestInlineImage = Float.NaN; // While setting the Spans on the final text, we also check whether any of them are images for (int i = ops.size() - 1; i >= 0; i--) { SetSpanOperation op = ops.get(i); if (op.what instanceof TextInlineImageSpan) { + int height = ((TextInlineImageSpan)op.what).getHeight(); textCSSNode.mContainsImages = true; + if (Float.isNaN(textCSSNode.mHeightOfTallestInlineImage) || height > textCSSNode.mHeightOfTallestInlineImage) { + textCSSNode.mHeightOfTallestInlineImage = height; + } } op.execute(sb); } @@ -226,6 +231,14 @@ public void measure( // 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))) { @@ -236,8 +249,8 @@ public void measure( textPaint, (int) Math.ceil(desiredWidth), Layout.Alignment.ALIGN_NORMAL, - 1, - 0, + lineSpacingMultiplier, + lineSpacingExtra, true); } else if (boring != null && (unconstrainedWidth || boring.width <= width)) { // Is used for single-line, boring text when the width is either unknown or bigger @@ -247,8 +260,8 @@ public void measure( textPaint, boring.width, Layout.Alignment.ALIGN_NORMAL, - 1, - 0, + lineSpacingMultiplier, + lineSpacingExtra, boring, true); } else { @@ -258,8 +271,8 @@ public void measure( textPaint, (int) width, Layout.Alignment.ALIGN_NORMAL, - 1, - 0, + lineSpacingMultiplier, + lineSpacingExtra, true); } @@ -269,13 +282,6 @@ public void measure( reactCSSNode.mNumberOfLines < layout.getLineCount()) { measureOutput.height = layout.getLineBottom(reactCSSNode.mNumberOfLines - 1); } - if (reactCSSNode.mLineHeight != UNSET) { - int lines = reactCSSNode.mNumberOfLines != UNSET - ? Math.min(reactCSSNode.mNumberOfLines, layout.getLineCount()) - : layout.getLineCount(); - float lineHeight = PixelUtil.toPixelFromSP(reactCSSNode.mLineHeight); - measureOutput.height = lineHeight * lines; - } } }; @@ -293,7 +299,7 @@ private static int parseNumericFontWeight(String fontWeightString) { 100 * (fontWeightString.charAt(0) - '0') : -1; } - private int mLineHeight = UNSET; + private float mLineHeight = Float.NaN; private boolean mIsColorSet = false; private int mColor; private boolean mIsBackgroundColorSet = false; @@ -340,6 +346,7 @@ private static int parseNumericFontWeight(String fontWeightString) { private final boolean mIsVirtual; protected boolean mContainsImages = false; + private float mHeightOfTallestInlineImage = Float.NaN; public ReactTextShadowNode(boolean isVirtual) { mIsVirtual = isVirtual; @@ -348,6 +355,15 @@ public ReactTextShadowNode(boolean isVirtual) { } } + // Returns a line height which takes into account the requested line height + // and the height of the inline images. + public float getEffectiveLineHeight() { + boolean useInlineViewHeight = !Float.isNaN(mLineHeight) && + !Float.isNaN(mHeightOfTallestInlineImage) && + mHeightOfTallestInlineImage > mLineHeight; + return useInlineViewHeight ? mHeightOfTallestInlineImage : mLineHeight; + } + @Override public void onBeforeLayout() { if (mIsVirtual) { @@ -380,7 +396,7 @@ public void setNumberOfLines(int numberOfLines) { @ReactProp(name = ViewProps.LINE_HEIGHT, defaultInt = UNSET) public void setLineHeight(int lineHeight) { - mLineHeight = lineHeight; + mLineHeight = lineHeight == UNSET ? Float.NaN : PixelUtil.toPixelFromSP(lineHeight); markUpdated(); } @@ -530,7 +546,7 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) { super.onCollectExtraUpdates(uiViewOperationQueue); if (mPreparedSpannableText != null) { ReactTextUpdate reactTextUpdate = - new ReactTextUpdate(mPreparedSpannableText, UNSET, mContainsImages, getPadding()); + new ReactTextUpdate(mPreparedSpannableText, UNSET, mContainsImages, getPadding(), getEffectiveLineHeight()); 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 dca68669d1d72e..5acb167c56acf0 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 @@ -27,12 +27,14 @@ public class ReactTextUpdate { private final float mPaddingTop; private final float mPaddingRight; private final float mPaddingBottom; + private final float mLineHeight; public ReactTextUpdate( Spannable text, int jsEventCounter, boolean containsImages, - Spacing padding) { + Spacing padding, + float lineHeight) { mText = text; mJsEventCounter = jsEventCounter; mContainsImages = containsImages; @@ -40,6 +42,7 @@ public ReactTextUpdate( mPaddingTop = padding.get(Spacing.TOP); mPaddingRight = padding.get(Spacing.RIGHT); mPaddingBottom = padding.get(Spacing.BOTTOM); + mLineHeight = lineHeight; } public Spannable getText() { @@ -69,4 +72,8 @@ public float getPaddingRight() { public float getPaddingBottom() { return mPaddingBottom; } + + public float getLineHeight() { + return mLineHeight; + } } 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 71bb5ccf10d245..9ef3df91a8ab58 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 @@ -17,6 +17,7 @@ import android.view.ViewGroup; import android.widget.TextView; +import com.facebook.csslayout.FloatUtil; import com.facebook.react.uimanager.ReactCompoundView; public class ReactTextView extends TextView implements ReactCompoundView { @@ -28,6 +29,7 @@ public class ReactTextView extends TextView implements ReactCompoundView { private int mDefaultGravityHorizontal; private int mDefaultGravityVertical; private boolean mTextIsSelectable; + private float mLineHeight = Float.NaN; public ReactTextView(Context context) { super(context); @@ -50,6 +52,16 @@ public void setText(ReactTextUpdate update) { (int) Math.ceil(update.getPaddingTop()), (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); + } + } } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java index 97e780a6d0f59e..dc200c1fcb7a74 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java @@ -103,15 +103,6 @@ public void setTextAlignVertical(ReactTextView view, @Nullable String textAlignV } } - @ReactProp(name = ViewProps.LINE_HEIGHT, defaultFloat = Float.NaN) - public void setLineHeight(ReactTextView view, float lineHeight) { - if (Float.isNaN(lineHeight)) { // NaN will be used if property gets reset - view.setLineSpacing(0, 1); - } else { - view.setLineSpacing(PixelUtil.toPixelFromSP(lineHeight), 0); - } - } - @ReactProp(name = "selectable") public void setSelectable(ReactTextView view, boolean isSelectable) { view.setTextIsSelectable(isSelectable); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextInlineImageSpan.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextInlineImageSpan.java index 0308ee20c28c87..c987260fd0f380 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextInlineImageSpan.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextInlineImageSpan.java @@ -67,4 +67,14 @@ public static void possiblyUpdateInlineImageSpans(Spannable spannable, TextView * Set the textview that will contain this span. */ public abstract void setTextView(TextView textView); + + /** + * Get the width of the span. + */ + public abstract int getWidth(); + + /** + * Get the height of the span. + */ + public abstract int getHeight(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageSpan.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageSpan.java index 0613457e59f7b2..0c0c5c1246768c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageSpan.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageSpan.java @@ -146,13 +146,21 @@ public void draw( canvas.save(); - int transY = bottom - mDrawable.getBounds().bottom; - // Align to baseline by default - transY -= paint.getFontMetricsInt().descent; + int transY = y - mDrawable.getBounds().bottom; canvas.translate(x, transY); mDrawable.draw(canvas); canvas.restore(); } + + @Override + public int getWidth() { + return mWidth; + } + + @Override + public int getHeight() { + return mHeight; + } } 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 c75e9797beda38..cc71f6b88c13b0 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 @@ -119,7 +119,7 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) { if (mJsEventCount != UNSET) { Spannable preparedSpannableText = fromTextCSSNode(this); ReactTextUpdate reactTextUpdate = - new ReactTextUpdate(preparedSpannableText, mJsEventCount, mContainsImages, getPadding()); + new ReactTextUpdate(preparedSpannableText, mJsEventCount, mContainsImages, getPadding(), getEffectiveLineHeight()); uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate); } }