diff --git a/RNTester/js/Shared/TextLegend.js b/RNTester/js/Shared/TextLegend.js index 3708ddeaf0c14a..56cf17607c6b64 100644 --- a/RNTester/js/Shared/TextLegend.js +++ b/RNTester/js/Shared/TextLegend.js @@ -17,6 +17,8 @@ class TextLegend extends React.Component<*, *> { state = { textMetrics: [], language: 'english', + alignment: 'left', + fontSize: 50, }; render() { @@ -50,6 +52,18 @@ class TextLegend extends React.Component<*, *> { }; return ( + + this.setState(prevState => ({fontSize: prevState.fontSize + 3})) + }> + Increase size + + + this.setState(prevState => ({fontSize: prevState.fontSize - 3})) + }> + Decrease size + this.setState({language: itemValue})}> @@ -179,17 +193,48 @@ class TextLegend extends React.Component<*, *> { }}> End of text , + , + + Start of text + , ]; }, )} - this.setState({textMetrics: event.nativeEvent.lines}) - } - style={{fontSize: 50}}> + onTextLayout={event => { + this.setState({textMetrics: event.nativeEvent.lines}); + }} + style={{ + fontSize: this.state.fontSize, + textAlign: this.state.alignment, + }}> {PANGRAMS[this.state.language]} + this.setState({alignment: itemValue})}> + + + + ); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.java index 2a235a355d4e21..419985323c3f2c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.java @@ -17,30 +17,38 @@ import com.facebook.react.bridge.WritableMap; public class FontMetricsUtil { + + private static final String CAP_HEIGHT_MEASUREMENT_TEXT = "T"; + private static final String X_HEIGHT_MEASUREMENT_TEXT = "x"; + private static final float AMPLIFICATION_FACTOR = 100; + public static WritableArray getFontMetrics(CharSequence text, Layout layout, TextPaint paint, Context context) { DisplayMetrics dm = context.getResources().getDisplayMetrics(); WritableArray lines = Arguments.createArray(); + // To calculate xHeight and capHeight we have to render an "x" and "T" and manually measure their height. + // In order to get more precision than Android offers, we blow up the text size by 100 and measure it. + // Luckily, text size affects rendering linearly, so we can do this trick. + TextPaint paintCopy = new TextPaint(paint); + paintCopy.setTextSize(paintCopy.getTextSize() * AMPLIFICATION_FACTOR); + Rect capHeightBounds = new Rect(); + paintCopy.getTextBounds(CAP_HEIGHT_MEASUREMENT_TEXT, 0, CAP_HEIGHT_MEASUREMENT_TEXT.length(), capHeightBounds); + double capHeight = capHeightBounds.height() / AMPLIFICATION_FACTOR / dm.density; + Rect xHeightBounds = new Rect(); + paintCopy.getTextBounds(X_HEIGHT_MEASUREMENT_TEXT, 0, X_HEIGHT_MEASUREMENT_TEXT.length(), xHeightBounds); + double xHeight = xHeightBounds.height() / AMPLIFICATION_FACTOR / dm.density; for (int i = 0; i < layout.getLineCount(); i++) { Rect bounds = new Rect(); layout.getLineBounds(i, bounds); - WritableMap line = Arguments.createMap(); - TextPaint paintCopy = new TextPaint(paint); - paintCopy.setTextSize(paintCopy.getTextSize() * 100); - Rect capHeightBounds = new Rect(); - paintCopy.getTextBounds("T", 0, 1, capHeightBounds); - Rect xHeightBounds = new Rect(); - paintCopy.getTextBounds("x", 0, 1, xHeightBounds); - line.putDouble("x", bounds.left / dm.density); + line.putDouble("x", layout.getLineLeft(i) / dm.density); line.putDouble("y", bounds.top / dm.density); line.putDouble("width", layout.getLineWidth(i) / dm.density); line.putDouble("height", bounds.height() / dm.density); line.putDouble("descender", layout.getLineDescent(i) / dm.density); line.putDouble("ascender", -layout.getLineAscent(i) / dm.density); line.putDouble("baseline", layout.getLineBaseline(i) / dm.density); - line.putDouble( - "capHeight", capHeightBounds.height() / 100 * paint.getTextSize() / dm.density); - line.putDouble("xHeight", xHeightBounds.height() / 100 * paint.getTextSize() / dm.density); + line.putDouble("capHeight", capHeight); + line.putDouble("xHeight", xHeight); line.putString( "text", text.subSequence(layout.getLineStart(i), layout.getLineEnd(i)).toString()); lines.pushMap(line); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java index 6e6d2cfef8441b..b1c68b987e9cdb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java @@ -181,6 +181,11 @@ private static void buildSpannedFromShadowNode( } } + protected int getDefaultFontSize() { + return mAllowFontScaling ? (int) Math.ceil(PixelUtil.toPixelFromSP(ViewDefaults.FONT_SIZE_SP)) + : (int) Math.ceil(PixelUtil.toPixelFromDIP(ViewDefaults.FONT_SIZE_SP)); + } + protected static Spannable spannedFromShadowNode( ReactBaseTextShadowNode textShadowNode, String text) { SpannableStringBuilder sb = new SpannableStringBuilder(); @@ -199,10 +204,7 @@ protected static Spannable spannedFromShadowNode( } if (textShadowNode.mFontSize == UNSET) { - int defaultFontSize = - textShadowNode.mAllowFontScaling - ? (int) Math.ceil(PixelUtil.toPixelFromSP(ViewDefaults.FONT_SIZE_SP)) - : (int) Math.ceil(PixelUtil.toPixelFromDIP(ViewDefaults.FONT_SIZE_SP)); + int defaultFontSize = textShadowNode.getDefaultFontSize(); ops.add(new SetSpanOperation(0, sb.length(), new AbsoluteSizeSpan(defaultFontSize))); } 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 14855f96820530..8b1ba78d131830 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 @@ -64,6 +64,7 @@ public long measure( YogaMeasureMode heightMode) { // TODO(5578671): Handle text direction (see View#getTextDirectionHeuristic) TextPaint textPaint = sTextPaintInstance; + textPaint.setTextSize(mFontSize != UNSET ? mFontSize : getDefaultFontSize()); Layout layout; Spanned text = Assertions.assertNotNull( mPreparedSpannableText, @@ -75,6 +76,19 @@ public long measure( // technically, width should never be negative, but there is currently a bug in boolean unconstrainedWidth = widthMode == YogaMeasureMode.UNDEFINED || width < 0; + Layout.Alignment alignment = Layout.Alignment.ALIGN_NORMAL; + switch (getTextAlign()) { + case Gravity.LEFT: + alignment = Layout.Alignment.ALIGN_NORMAL; + break; + case Gravity.RIGHT: + alignment = Layout.Alignment.ALIGN_OPPOSITE; + break; + case Gravity.CENTER_HORIZONTAL: + alignment = Layout.Alignment.ALIGN_CENTER; + break; + } + if (boring == null && (unconstrainedWidth || (!YogaConstants.isUndefined(desiredWidth) && desiredWidth <= width))) { @@ -87,13 +101,13 @@ public long measure( text, textPaint, hintWidth, - Layout.Alignment.ALIGN_NORMAL, + alignment, 1.f, 0.f, mIncludeFontPadding); } else { layout = StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, hintWidth) - .setAlignment(Layout.Alignment.ALIGN_NORMAL) + .setAlignment(alignment) .setLineSpacing(0.f, 1.f) .setIncludePad(mIncludeFontPadding) .setBreakStrategy(mTextBreakStrategy) @@ -108,7 +122,7 @@ public long measure( text, textPaint, boring.width, - Layout.Alignment.ALIGN_NORMAL, + alignment, 1.f, 0.f, boring, @@ -121,13 +135,13 @@ public long measure( text, textPaint, (int) width, - Layout.Alignment.ALIGN_NORMAL, + alignment, 1.f, 0.f, mIncludeFontPadding); } else { layout = StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, (int) width) - .setAlignment(Layout.Alignment.ALIGN_NORMAL) + .setAlignment(alignment) .setLineSpacing(0.f, 1.f) .setIncludePad(mIncludeFontPadding) .setBreakStrategy(mTextBreakStrategy)