Skip to content

Commit

Permalink
Text correctly aligns with nested inline image. The height of the inl…
Browse files Browse the repository at this point in the history
…ine image changes the Text lineHeight

#35704 (comment)
  • Loading branch information
fabOnReact committed Jan 4, 2023
1 parent ba18167 commit 9a7ad5b
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,28 @@

package com.facebook.react.views.text;

import android.graphics.Rect;
import android.text.TextPaint;
import android.text.style.SuperscriptSpan;
import android.text.style.MetricAffectingSpan;
import android.view.Gravity;
import com.facebook.common.logging.FLog;

/** ratio 0 for center ratio 0.4 for top ratio */
public class ReactAlignSpan extends SuperscriptSpan implements ReactSpan {
public class ReactAlignSpan extends MetricAffectingSpan implements ReactSpan {
private static final String TAG = "ReactAlignSpan";
private final double mLineHeight;
private Integer mParentHeight;
private String mTextAlignVertical;
private Integer mParentGravity;
private int mParentLineCount;
private int mCurrentLine;
private float mCalculatedHeight;
private int mMaximumLineHeight = 0;
private int mOtherSpanLineHeight;

ReactAlignSpan(String textAlignVertical) {
ReactAlignSpan(String textAlignVertical, Float lineHeight) {
mTextAlignVertical = textAlignVertical;
mLineHeight = lineHeight;
}

private double convertTextAlignToStep(String textAlign) {
Expand Down Expand Up @@ -87,16 +92,30 @@ public void updateDrawState(TextPaint ds) {
if (numberOfSteps < 0) {
additionalLines = lineHeight * (mParentLineCount - mCurrentLine - 1) * -1;
}
ds.baselineShift -= margin * numberOfSteps + additionalLines;

Rect bounds = new Rect();
ds.getTextBounds("Top", 0, 2, bounds);
// inline image over-riding lineHeight
if (mOtherSpanLineHeight > mLineHeight) {
ds.baselineShift -= mOtherSpanLineHeight - ds.getTextSize();
} else {
ds.baselineShift -= mLineHeight / 2 - ds.getTextSize() / 2;
}
}

public void updateSpan(
Integer height, int gravity, int lineCount, float calculatedHeight, int currentLine) {
Integer height,
int gravity,
int lineCount,
float calculatedHeight,
int currentLine,
int otherSpanLineHeight) {
mParentHeight = height;
mParentGravity = gravity;
mParentLineCount = lineCount;
mCalculatedHeight = calculatedHeight;
mCurrentLine = currentLine;
mOtherSpanLineHeight = otherSpanLineHeight;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;

public class ReactTextView extends AppCompatTextView implements ReactCompoundView {

Expand Down Expand Up @@ -184,18 +185,42 @@ private ReactContext getReactContext() {
protected void onLayout(
boolean changed, int textViewLeft, int textViewTop, int textViewRight, int textViewBottom) {
// TODO T62882314: Delete this method when Fabric is fully released in OSS
Spanned text = (Spanned) getText();
Layout layout = getLayout();
TextInlineViewPlaceholderSpan[] placeholders =
text.getSpans(0, text.length(), TextInlineViewPlaceholderSpan.class);
HashMap<Integer, Integer> imageLineHeights = new HashMap<Integer, Integer>();
for (TextInlineViewPlaceholderSpan placeholder : placeholders) {
int height = placeholder.getHeight();
int start = text.getSpanStart(placeholder);
int line = layout.getLineForOffset(start);
if (imageLineHeights.get(line) != null && imageLineHeights.get(line) > height) {
imageLineHeights.put(line, height);
}
if (imageLineHeights.get(line) == null) {
imageLineHeights.put(line, height);
}
}
if (getText() instanceof Spanned) {
Spanned text = (Spanned) getText();
ReactAlignSpan[] spans = text.getSpans(0, text.length(), ReactAlignSpan.class);
Layout layout = getLayout();
if (layout != null) {
int lineCount = layout != null ? layout.getLineCount() : 1;
int lineHeight = layout != null ? layout.getHeight() : 0;
if (spans.length != 0) {
for (ReactAlignSpan span : spans) {
int start = text.getSpanStart(span);
int currentLine = layout.getLineForOffset(start);
span.updateSpan(getHeight(), getGravity(), lineCount, layout.getHeight(), currentLine);
int highestLineHeight =
imageLineHeights != null && imageLineHeights.get(currentLine) != null
? imageLineHeights.get(currentLine)
: -1;
span.updateSpan(
getHeight(),
getGravity(),
lineCount,
layout.getHeight(),
currentLine,
highestLineHeight);
}
}
}
Expand Down Expand Up @@ -223,8 +248,6 @@ protected void onLayout(
UIManagerModule uiManager =
Assertions.assertNotNull(reactContext.getNativeModule(UIManagerModule.class));

Spanned text = (Spanned) getText();
Layout layout = getLayout();
FLog.w("React::" + TAG, "onLayout " + " layout.getHeight(): " + (layout.getHeight()));
if (layout == null) {
// Text layout is calculated during pre-draw phase, so in some cases it can be empty during
Expand All @@ -237,8 +260,6 @@ protected void onLayout(
return;
}

TextInlineViewPlaceholderSpan[] placeholders =
text.getSpans(0, text.length(), TextInlineViewPlaceholderSpan.class);
ArrayList inlineViewInfoArray =
mNotifyOnInlineViewLayout ? new ArrayList(placeholders.length) : null;
int textViewWidth = textViewRight - textViewLeft;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,11 @@ private static void buildSpannableFromFragment(
|| textAttributes.mVerticalAlign.equals("bottom-child")
|| textAttributes.mVerticalAlign.equals("center-child"))) {
ops.add(
new SetSpanOperation(start, end, new ReactAlignSpan(textAttributes.mVerticalAlign)));
new SetSpanOperation(
start,
end,
new ReactAlignSpan(
textAttributes.mVerticalAlign, textAttributes.getEffectiveLineHeight())));
}
if (textAttributes.mIsAccessibilityLink) {
ops.add(new SetSpanOperation(start, end, new ReactClickableSpan(reactTag)));
Expand Down Expand Up @@ -412,11 +416,6 @@ public static long measureText(
? paragraphAttributes.getInt(PA_KEY_MAX_NUMBER_OF_LINES)
: UNSET;

// Calculate the positions of the attachments (views) that will be rendered inside the
// Spanned Text. The following logic is only executed when a text contains views inside.
// This follows a similar logic than used in pre-fabric (see ReactTextView.onLayout method).
// int currentLine = layout.getLineForOffset(start);

int calculatedLineCount =
maximumNumberOfLines == UNSET || maximumNumberOfLines == 0
? layout.getLineCount()
Expand Down
12 changes: 6 additions & 6 deletions packages/rn-tester/js/examples/Text/TextExample.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,17 +211,17 @@ class TextExample extends React.Component<{...}> {
return (
<RNTesterPage title="<Text>">
<RNTesterBlock title="Dynamic Font Size Adjustment">
<View style={{height: 250}}>
<View>
<Text
textTransform="uppercase"
style={{
flex: 1,
textAlignVertical: 'center',
textAlignVertical: 'bottom',
backgroundColor: 'yellow',
}}>
A parent text line more in the text a line more in the text a line
A parent text line more in the text a line
<Text
style={{
lineHeight: 100,
textAlignVertical: 'top',
backgroundColor: 'green',
}}>
Expand All @@ -232,9 +232,9 @@ class TextExample extends React.Component<{...}> {
style={{
textAlignVertical: 'center',
backgroundColor: 'blue',
color: 'white',
color: 'red',
}}>
Bottom
Center
</Text>
more in the text a line more in the text line more in the
<Text
Expand Down

0 comments on commit 9a7ad5b

Please sign in to comment.