Add support to measure shadow nodes in the FabricUIManager
Summary: In this diff I added support to be able to measure C++ shadowNode in Android. As an example I implemented the measurement of TextViews Reviewed By: shergin Differential Revision: D9583972 fbshipit-source-id: 1344782d4c586c94a4576b18a4acfa4775e46952
This commit is contained in:
parent
52dd7dbbcf
commit
5c0da011cb
|
@ -31,6 +31,13 @@ public class PixelUtil {
|
|||
return toPixelFromDIP((float) value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from PX to SP
|
||||
*/
|
||||
public static float toSPFromPixel(float value) {
|
||||
return value / DisplayMetricsHolder.getScreenDisplayMetrics().scaledDensity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from SP to PX
|
||||
*/
|
||||
|
|
|
@ -10,7 +10,9 @@ package com.facebook.react.uimanager;
|
|||
import android.view.View;
|
||||
import com.facebook.react.bridge.BaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableNativeMap;
|
||||
import com.facebook.react.touch.JSResponderHandler;
|
||||
import com.facebook.react.touch.ReactInterceptingViewGroup;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
|
@ -202,4 +204,16 @@ public abstract class ViewManager<T extends View, C extends ReactShadowNode>
|
|||
public Map<String, String> getNativeProps() {
|
||||
return ViewManagerPropertyUpdater.getNativeProps(getClass(), getShadowNodeClass());
|
||||
}
|
||||
|
||||
public float[] measure(
|
||||
ReactContext context,
|
||||
T view,
|
||||
ReadableNativeMap localData,
|
||||
ReadableNativeMap props,
|
||||
float width,
|
||||
int widthMode,
|
||||
float height,
|
||||
int heightMode) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import android.content.Context;
|
|||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.text.Layout;
|
||||
import android.text.Spannable;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Gravity;
|
||||
|
@ -36,6 +37,7 @@ public class ReactTextView extends TextView implements ReactCompoundView {
|
|||
private TextUtils.TruncateAt mEllipsizeLocation = TextUtils.TruncateAt.END;
|
||||
|
||||
private ReactViewBackgroundManager mReactBackgroundManager;
|
||||
private Spannable mSpanned;
|
||||
|
||||
public ReactTextView(Context context) {
|
||||
super(context);
|
||||
|
@ -255,4 +257,12 @@ public class ReactTextView extends TextView implements ReactCompoundView {
|
|||
public void setBorderStyle(@Nullable String style) {
|
||||
mReactBackgroundManager.setBorderStyle(style);
|
||||
}
|
||||
|
||||
public void setSpanned(Spannable spanned) {
|
||||
mSpanned = spanned;
|
||||
}
|
||||
|
||||
public Spannable getSpanned() {
|
||||
return mSpanned;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,11 +9,14 @@ package com.facebook.react.views.text;
|
|||
|
||||
import android.text.Spannable;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReadableNativeMap;
|
||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
import com.facebook.yoga.YogaMeasureMode;
|
||||
|
||||
/**
|
||||
* Concrete class for {@link ReactTextAnchorViewManager} which represents view managers of anchor
|
||||
|
@ -66,4 +69,25 @@ public class ReactTextViewManager
|
|||
public @Nullable Map getExportedCustomDirectEventTypeConstants() {
|
||||
return MapBuilder.of("topTextLayout", MapBuilder.of("registrationName", "onTextLayout"));
|
||||
}
|
||||
|
||||
public float[] measure(
|
||||
ReactContext context,
|
||||
ReactTextView view,
|
||||
ReadableNativeMap localData,
|
||||
ReadableNativeMap props,
|
||||
float width,
|
||||
int widthMode,
|
||||
float height,
|
||||
int heightMode) {
|
||||
|
||||
// TODO: should widthMode and heightMode be a YogaMeasureMode?
|
||||
return TextLayoutManager.measureText(context,
|
||||
view,
|
||||
localData,
|
||||
props,
|
||||
width,
|
||||
YogaMeasureMode.fromInt(widthMode),
|
||||
height,
|
||||
YogaMeasureMode.fromInt(heightMode));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,416 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.text;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Build;
|
||||
import android.text.Layout;
|
||||
import android.view.Gravity;
|
||||
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.ReactStylesDiffMap;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
import com.facebook.yoga.YogaDirection;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class TextAttributeProps {
|
||||
|
||||
private static final String INLINE_IMAGE_PLACEHOLDER = "I";
|
||||
public static final int UNSET = -1;
|
||||
|
||||
private static final String PROP_SHADOW_OFFSET = "textShadowOffset";
|
||||
private static final String PROP_SHADOW_OFFSET_WIDTH = "width";
|
||||
private static final String PROP_SHADOW_OFFSET_HEIGHT = "height";
|
||||
private static final String PROP_SHADOW_RADIUS = "textShadowRadius";
|
||||
private static final String PROP_SHADOW_COLOR = "textShadowColor";
|
||||
|
||||
private static final String PROP_TEXT_TRANSFORM = "textTransform";
|
||||
|
||||
private static final int DEFAULT_TEXT_SHADOW_COLOR = 0x55000000;
|
||||
|
||||
protected float mLineHeight = Float.NaN;
|
||||
protected float mLetterSpacing = Float.NaN;
|
||||
protected boolean mIsColorSet = false;
|
||||
protected boolean mAllowFontScaling = true;
|
||||
protected int mColor;
|
||||
protected boolean mIsBackgroundColorSet = false;
|
||||
protected int mBackgroundColor;
|
||||
|
||||
protected int mNumberOfLines = UNSET;
|
||||
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;
|
||||
protected TextTransform mTextTransform = TextTransform.UNSET;
|
||||
|
||||
protected float mTextShadowOffsetDx = 0;
|
||||
protected float mTextShadowOffsetDy = 0;
|
||||
protected float mTextShadowRadius = 1;
|
||||
protected int mTextShadowColor = DEFAULT_TEXT_SHADOW_COLOR;
|
||||
|
||||
protected boolean mIsUnderlineTextDecorationSet = false;
|
||||
protected boolean mIsLineThroughTextDecorationSet = false;
|
||||
protected boolean mIncludeFontPadding = true;
|
||||
|
||||
/**
|
||||
* mFontStyle can be {@link Typeface#NORMAL} or {@link Typeface#ITALIC}.
|
||||
* mFontWeight can be {@link Typeface#NORMAL} or {@link Typeface#BOLD}.
|
||||
*/
|
||||
protected int mFontStyle = UNSET;
|
||||
|
||||
protected int mFontWeight = UNSET;
|
||||
/**
|
||||
* NB: If a font family is used that does not have a style in a certain Android version (ie.
|
||||
* monospace bold pre Android 5.0), that style (ie. bold) will not be inherited by nested Text
|
||||
* nodes. To retain that style, you have to add it to those nodes explicitly.
|
||||
* Example, Android 4.4:
|
||||
* <Text style={{fontFamily="serif" fontWeight="bold"}}>Bold Text</Text>
|
||||
* <Text style={{fontFamily="sans-serif"}}>Bold Text</Text>
|
||||
* <Text style={{fontFamily="serif}}>Bold Text</Text>
|
||||
*
|
||||
* <Text style={{fontFamily="monospace" fontWeight="bold"}}>Not Bold Text</Text>
|
||||
* <Text style={{fontFamily="sans-serif"}}>Not Bold Text</Text>
|
||||
* <Text style={{fontFamily="serif}}>Not Bold Text</Text>
|
||||
*
|
||||
* <Text style={{fontFamily="monospace" fontWeight="bold"}}>Not Bold Text</Text>
|
||||
* <Text style={{fontFamily="sans-serif" fontWeight="bold"}}>Bold Text</Text>
|
||||
* <Text style={{fontFamily="serif}}>Bold Text</Text>
|
||||
*/
|
||||
protected @Nullable String mFontFamily = null;
|
||||
|
||||
protected boolean mContainsImages = false;
|
||||
protected float mHeightOfTallestInlineImage = Float.NaN;
|
||||
|
||||
private final ReactStylesDiffMap mProps;
|
||||
|
||||
|
||||
public TextAttributeProps(ReactStylesDiffMap props) {
|
||||
mProps = props;
|
||||
setNumberOfLines(getIntProp(ViewProps.NUMBER_OF_LINES, UNSET));
|
||||
setLineHeight(getFloatProp(ViewProps.LINE_HEIGHT, UNSET));
|
||||
setLetterSpacing(getFloatProp(ViewProps.LETTER_SPACING, Float.NaN));
|
||||
setAllowFontScaling(getBooleanProp(ViewProps.ALLOW_FONT_SCALING, true));
|
||||
setTextAlign(getStringProp(ViewProps.TEXT_ALIGN));
|
||||
setFontSize(getFloatProp(ViewProps.FONT_SIZE, UNSET));
|
||||
setColor(props.hasKey(ViewProps.COLOR) ? props.getInt(ViewProps.COLOR, 0) : null);
|
||||
setColor(props.hasKey("foregroundColor") ? props.getInt("foregroundColor", 0) : null);
|
||||
setBackgroundColor(props.hasKey(ViewProps.BACKGROUND_COLOR) ? props.getInt(ViewProps.BACKGROUND_COLOR, 0) : null);
|
||||
setFontFamily(getStringProp(ViewProps.FONT_FAMILY));
|
||||
setFontWeight(getStringProp(ViewProps.FONT_WEIGHT));
|
||||
setFontStyle(getStringProp(ViewProps.FONT_STYLE));
|
||||
setIncludeFontPadding(getBooleanProp(ViewProps.INCLUDE_FONT_PADDING, true));
|
||||
setTextDecorationLine(getStringProp(ViewProps.TEXT_DECORATION_LINE));
|
||||
setTextBreakStrategy(getStringProp(ViewProps.TEXT_BREAK_STRATEGY));
|
||||
setTextShadowOffset(props.hasKey(PROP_SHADOW_OFFSET) ? props.getMap(PROP_SHADOW_OFFSET) : null);
|
||||
setTextShadowRadius(getIntProp(PROP_SHADOW_RADIUS, 1));
|
||||
setTextShadowColor(getIntProp(PROP_SHADOW_COLOR, DEFAULT_TEXT_SHADOW_COLOR));
|
||||
setTextTransform(getStringProp(PROP_TEXT_TRANSFORM));
|
||||
}
|
||||
|
||||
private boolean getBooleanProp(String name, boolean defaultValue) {
|
||||
if (mProps.hasKey(name)) {
|
||||
return mProps.getBoolean(name, defaultValue);
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private String getStringProp(String name) {
|
||||
if (mProps.hasKey(name)) {
|
||||
return mProps.getString(name);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private int getIntProp(String name, int defaultvalue) {
|
||||
if (mProps.hasKey(name)) {
|
||||
return mProps.getInt(name, defaultvalue);
|
||||
} else {
|
||||
return defaultvalue;
|
||||
}
|
||||
}
|
||||
|
||||
private float getFloatProp(String name, float defaultvalue) {
|
||||
if (mProps.hasKey(name)) {
|
||||
return mProps.getFloat(name, defaultvalue);
|
||||
} else {
|
||||
return defaultvalue;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Return text alignment according to LTR or RTL style
|
||||
public int getTextAlign() {
|
||||
int textAlign = mTextAlign;
|
||||
if (getLayoutDirection() == YogaDirection.RTL) {
|
||||
if (textAlign == Gravity.RIGHT) {
|
||||
textAlign = Gravity.LEFT;
|
||||
} else if (textAlign == Gravity.LEFT) {
|
||||
textAlign = Gravity.RIGHT;
|
||||
}
|
||||
}
|
||||
return textAlign;
|
||||
}
|
||||
|
||||
public void setNumberOfLines(int numberOfLines) {
|
||||
mNumberOfLines = numberOfLines == 0 ? UNSET : numberOfLines;
|
||||
}
|
||||
|
||||
public void setLineHeight(float lineHeight) {
|
||||
mLineHeightInput = lineHeight;
|
||||
if (lineHeight == UNSET) {
|
||||
mLineHeight = Float.NaN;
|
||||
} else {
|
||||
mLineHeight =
|
||||
mAllowFontScaling
|
||||
? PixelUtil.toPixelFromSP(lineHeight)
|
||||
: PixelUtil.toPixelFromDIP(lineHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public void setLetterSpacing(float letterSpacing) {
|
||||
mLetterSpacingInput = letterSpacing;
|
||||
mLetterSpacing = mAllowFontScaling
|
||||
? PixelUtil.toPixelFromSP(mLetterSpacingInput)
|
||||
: PixelUtil.toPixelFromDIP(mLetterSpacingInput);
|
||||
}
|
||||
|
||||
public void setAllowFontScaling(boolean allowFontScaling) {
|
||||
if (allowFontScaling != mAllowFontScaling) {
|
||||
mAllowFontScaling = allowFontScaling;
|
||||
setFontSize(mFontSizeInput);
|
||||
setLineHeight(mLineHeightInput);
|
||||
setLetterSpacing(mLetterSpacingInput);
|
||||
}
|
||||
}
|
||||
|
||||
public void setTextAlign(@Nullable String textAlign) {
|
||||
if (textAlign == null || "auto".equals(textAlign)) {
|
||||
mTextAlign = Gravity.NO_GRAVITY;
|
||||
} else if ("left".equals(textAlign)) {
|
||||
mTextAlign = Gravity.LEFT;
|
||||
} else if ("right".equals(textAlign)) {
|
||||
mTextAlign = Gravity.RIGHT;
|
||||
} else if ("center".equals(textAlign)) {
|
||||
mTextAlign = Gravity.CENTER_HORIZONTAL;
|
||||
} else if ("justify".equals(textAlign)) {
|
||||
// Fallback gracefully for cross-platform compat instead of error
|
||||
mTextAlign = Gravity.LEFT;
|
||||
} else {
|
||||
throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign);
|
||||
}
|
||||
}
|
||||
|
||||
public void setFontSize(float fontSize) {
|
||||
mFontSizeInput = fontSize;
|
||||
if (fontSize != UNSET) {
|
||||
fontSize =
|
||||
mAllowFontScaling
|
||||
? (float) Math.ceil(PixelUtil.toPixelFromSP(fontSize))
|
||||
: (float) Math.ceil(PixelUtil.toPixelFromDIP(fontSize));
|
||||
}
|
||||
mFontSize = (int) fontSize;
|
||||
}
|
||||
|
||||
public void setColor(@Nullable Integer color) {
|
||||
mIsColorSet = (color != null);
|
||||
if (mIsColorSet) {
|
||||
mColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
public void setBackgroundColor(Integer color) {
|
||||
//TODO: Don't apply background color to anchor TextView since it will be applied on the View directly
|
||||
//if (!isVirtualAnchor()) {
|
||||
mIsBackgroundColorSet = (color != null);
|
||||
if (mIsBackgroundColorSet) {
|
||||
mBackgroundColor = color;
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
public void setFontFamily(@Nullable String fontFamily) {
|
||||
mFontFamily = fontFamily;
|
||||
}
|
||||
|
||||
/**
|
||||
/* This code is duplicated in ReactTextInputManager
|
||||
/* TODO: Factor into a common place they can both use
|
||||
*/
|
||||
public void setFontWeight(@Nullable String fontWeightString) {
|
||||
int fontWeightNumeric =
|
||||
fontWeightString != null ? parseNumericFontWeight(fontWeightString) : -1;
|
||||
int fontWeight = UNSET;
|
||||
if (fontWeightNumeric >= 500 || "bold".equals(fontWeightString)) {
|
||||
fontWeight = Typeface.BOLD;
|
||||
} else if ("normal".equals(fontWeightString)
|
||||
|| (fontWeightNumeric != -1 && fontWeightNumeric < 500)) {
|
||||
fontWeight = Typeface.NORMAL;
|
||||
}
|
||||
if (fontWeight != mFontWeight) {
|
||||
mFontWeight = fontWeight;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/* This code is duplicated in ReactTextInputManager
|
||||
/* TODO: Factor into a common place they can both use
|
||||
*/
|
||||
public void setFontStyle(@Nullable String fontStyleString) {
|
||||
int fontStyle = UNSET;
|
||||
if ("italic".equals(fontStyleString)) {
|
||||
fontStyle = Typeface.ITALIC;
|
||||
} else if ("normal".equals(fontStyleString)) {
|
||||
fontStyle = Typeface.NORMAL;
|
||||
}
|
||||
if (fontStyle != mFontStyle) {
|
||||
mFontStyle = fontStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public void setIncludeFontPadding(boolean includepad) {
|
||||
mIncludeFontPadding = includepad;
|
||||
}
|
||||
|
||||
public void setTextDecorationLine(@Nullable String textDecorationLineString) {
|
||||
mIsUnderlineTextDecorationSet = false;
|
||||
mIsLineThroughTextDecorationSet = false;
|
||||
if (textDecorationLineString != null) {
|
||||
for (String textDecorationLineSubString : textDecorationLineString.split(" ")) {
|
||||
if ("underline".equals(textDecorationLineSubString)) {
|
||||
mIsUnderlineTextDecorationSet = true;
|
||||
} else if ("line-through".equals(textDecorationLineSubString)) {
|
||||
mIsLineThroughTextDecorationSet = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setTextBreakStrategy(@Nullable String textBreakStrategy) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (textBreakStrategy == null || "highQuality".equals(textBreakStrategy)) {
|
||||
mTextBreakStrategy = Layout.BREAK_STRATEGY_HIGH_QUALITY;
|
||||
} else if ("simple".equals(textBreakStrategy)) {
|
||||
mTextBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
|
||||
} else if ("balanced".equals(textBreakStrategy)) {
|
||||
mTextBreakStrategy = Layout.BREAK_STRATEGY_BALANCED;
|
||||
} else {
|
||||
throw new JSApplicationIllegalArgumentException(
|
||||
"Invalid textBreakStrategy: " + textBreakStrategy);
|
||||
}
|
||||
}
|
||||
|
||||
public void setTextShadowOffset(ReadableMap offsetMap) {
|
||||
mTextShadowOffsetDx = 0;
|
||||
mTextShadowOffsetDy = 0;
|
||||
|
||||
if (offsetMap != null) {
|
||||
if (offsetMap.hasKey(PROP_SHADOW_OFFSET_WIDTH)
|
||||
&& !offsetMap.isNull(PROP_SHADOW_OFFSET_WIDTH)) {
|
||||
mTextShadowOffsetDx =
|
||||
PixelUtil.toPixelFromDIP(offsetMap.getDouble(PROP_SHADOW_OFFSET_WIDTH));
|
||||
}
|
||||
if (offsetMap.hasKey(PROP_SHADOW_OFFSET_HEIGHT)
|
||||
&& !offsetMap.isNull(PROP_SHADOW_OFFSET_HEIGHT)) {
|
||||
mTextShadowOffsetDy =
|
||||
PixelUtil.toPixelFromDIP(offsetMap.getDouble(PROP_SHADOW_OFFSET_HEIGHT));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setTextShadowRadius(float textShadowRadius) {
|
||||
if (textShadowRadius != mTextShadowRadius) {
|
||||
mTextShadowRadius = textShadowRadius;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTextShadowColor(int textShadowColor) {
|
||||
if (textShadowColor != mTextShadowColor) {
|
||||
mTextShadowColor = textShadowColor;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void setTextTransform(@Nullable String textTransform) {
|
||||
if (textTransform == null || "none".equals(textTransform)) {
|
||||
mTextTransform = TextTransform.NONE;
|
||||
} else if ("uppercase".equals(textTransform)) {
|
||||
mTextTransform = TextTransform.UPPERCASE;
|
||||
} else if ("lowercase".equals(textTransform)) {
|
||||
mTextTransform = TextTransform.LOWERCASE;
|
||||
} else if ("capitalize".equals(textTransform)) {
|
||||
mTextTransform = TextTransform.CAPITALIZE;
|
||||
} else {
|
||||
throw new JSApplicationIllegalArgumentException("Invalid textTransform: " + textTransform);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return -1 if the input string is not a valid numeric fontWeight (100, 200, ..., 900), otherwise
|
||||
* return the weight.
|
||||
*
|
||||
* This code is duplicated in ReactTextInputManager
|
||||
* TODO: Factor into a common place they can both use
|
||||
*/
|
||||
private static int parseNumericFontWeight(String fontWeightString) {
|
||||
// This should be much faster than using regex to verify input and Integer.parseInt
|
||||
return fontWeightString.length() == 3
|
||||
&& fontWeightString.endsWith("00")
|
||||
&& fontWeightString.charAt(0) <= '9'
|
||||
&& fontWeightString.charAt(0) >= '1'
|
||||
? 100 * (fontWeightString.charAt(0) - '0')
|
||||
: -1;
|
||||
}
|
||||
|
||||
//TODO remove this from here
|
||||
private YogaDirection getLayoutDirection() {
|
||||
return YogaDirection.LTR;
|
||||
}
|
||||
|
||||
public float getBottomPadding() {
|
||||
// TODO convert into constants
|
||||
return getFloatProp("bottomPadding", 0f);
|
||||
}
|
||||
|
||||
public float getLeftPadding() {
|
||||
return getFloatProp("leftPadding", 0f);
|
||||
}
|
||||
|
||||
public float getStartPadding() {
|
||||
return getFloatProp("startPadding", 0f);
|
||||
}
|
||||
|
||||
public float getEndPadding() {
|
||||
return getFloatProp("endPadding", 0f);
|
||||
}
|
||||
|
||||
public float getTopPadding() {
|
||||
return getFloatProp("topPadding", 0f);
|
||||
}
|
||||
|
||||
public float getRightPadding() {
|
||||
return getFloatProp("rightPadding", 0f);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,337 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.text;
|
||||
|
||||
import static com.facebook.react.views.text.TextAttributeProps.UNSET;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.text.BoringLayout;
|
||||
import android.text.Layout;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.StaticLayout;
|
||||
import android.text.TextPaint;
|
||||
import android.text.style.AbsoluteSizeSpan;
|
||||
import android.text.style.BackgroundColorSpan;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.StrikethroughSpan;
|
||||
import android.text.style.UnderlineSpan;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReadableNativeMap;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.ReactStylesDiffMap;
|
||||
import com.facebook.yoga.YogaConstants;
|
||||
import com.facebook.yoga.YogaMeasureMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class responsible of creating {@link Spanned} object for the JS representation of Text
|
||||
*/
|
||||
public class TextLayoutManager {
|
||||
|
||||
// It's important to pass the ANTI_ALIAS_FLAG flag to the constructor rather than setting it
|
||||
// later by calling setFlags. This is because the latter approach triggers a bug on Android 4.4.2.
|
||||
// The bug is that unicode emoticons aren't measured properly which causes text to be clipped.
|
||||
private static final TextPaint sTextPaintInstance = new TextPaint(TextPaint.ANTI_ALIAS_FLAG);
|
||||
|
||||
private static void buildSpannedFromShadowNode(
|
||||
Context context,
|
||||
ReadableArray fragments,
|
||||
SpannableStringBuilder sb,
|
||||
List<SetSpanOperation> ops) {
|
||||
|
||||
for (int i = 0, length = fragments.size(); i < length; i++) {
|
||||
ReadableMap fragment = fragments.getMap(i);
|
||||
int start = sb.length();
|
||||
|
||||
//ReactRawText
|
||||
sb.append(fragment.getString("string"));
|
||||
|
||||
// TODO: add support for TextInlineImage and BaseText
|
||||
// if (child instanceof ReactRawTextShadowNode) {
|
||||
// sb.append(((ReactRawTextShadowNode) child).getText());
|
||||
// } else if (child instanceof ReactBaseTextShadowNode) {
|
||||
// buildSpannedFromShadowNode((ReactBaseTextShadowNode) child, sb, ops);
|
||||
// } else if (child instanceof ReactTextInlineImageShadowNode) {
|
||||
// // We make the image take up 1 character in the span and put a corresponding character into
|
||||
// // the text so that the image doesn't run over any following text.
|
||||
// sb.append(INLINE_IMAGE_PLACEHOLDER);
|
||||
// ops.add(
|
||||
// new SetSpanOperation(
|
||||
// sb.length() - INLINE_IMAGE_PLACEHOLDER.length(),
|
||||
// sb.length(),
|
||||
// ((ReactTextInlineImageShadowNode) child).buildInlineImageSpan()));
|
||||
// } else {
|
||||
// throw new IllegalViewOperationException(
|
||||
// "Unexpected view type nested under text node: " + child.getClass());
|
||||
// }
|
||||
|
||||
TextAttributeProps textAttributes = new TextAttributeProps(new ReactStylesDiffMap(fragment.getMap("textAttributes")));
|
||||
int end = sb.length();
|
||||
if (end >= start) {
|
||||
if (textAttributes.mIsColorSet) {
|
||||
ops.add(new SetSpanOperation(start, end, new ForegroundColorSpan(textAttributes.mColor)));
|
||||
}
|
||||
if (textAttributes.mIsBackgroundColorSet) {
|
||||
ops.add(
|
||||
new SetSpanOperation(
|
||||
start, end, new BackgroundColorSpan(textAttributes.mBackgroundColor)));
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
if (!Float.isNaN(textAttributes.mLetterSpacing)) {
|
||||
ops.add(new SetSpanOperation(
|
||||
start,
|
||||
end,
|
||||
new CustomLetterSpacingSpan(textAttributes.mLetterSpacing)));
|
||||
}
|
||||
}
|
||||
if (textAttributes.mFontSize != UNSET) {
|
||||
ops.add(
|
||||
new SetSpanOperation(
|
||||
start, end, new AbsoluteSizeSpan((int) (textAttributes.mFontSize))));
|
||||
}
|
||||
if (textAttributes.mFontStyle != UNSET
|
||||
|| textAttributes.mFontWeight != UNSET
|
||||
|| textAttributes.mFontFamily != null) {
|
||||
ops.add(
|
||||
new SetSpanOperation(
|
||||
start,
|
||||
end,
|
||||
new CustomStyleSpan(
|
||||
textAttributes.mFontStyle,
|
||||
textAttributes.mFontWeight,
|
||||
textAttributes.mFontFamily,
|
||||
context.getAssets())));
|
||||
}
|
||||
if (textAttributes.mIsUnderlineTextDecorationSet) {
|
||||
ops.add(new SetSpanOperation(start, end, new UnderlineSpan()));
|
||||
}
|
||||
if (textAttributes.mIsLineThroughTextDecorationSet) {
|
||||
ops.add(new SetSpanOperation(start, end, new StrikethroughSpan()));
|
||||
}
|
||||
if (textAttributes.mTextShadowOffsetDx != 0 || textAttributes.mTextShadowOffsetDy != 0) {
|
||||
ops.add(
|
||||
new SetSpanOperation(
|
||||
start,
|
||||
end,
|
||||
new ShadowStyleSpan(
|
||||
textAttributes.mTextShadowOffsetDx,
|
||||
textAttributes.mTextShadowOffsetDy,
|
||||
textAttributes.mTextShadowRadius,
|
||||
textAttributes.mTextShadowColor)));
|
||||
}
|
||||
if (!Float.isNaN(textAttributes.getEffectiveLineHeight())) {
|
||||
ops.add(
|
||||
new SetSpanOperation(
|
||||
start, end, new CustomLineHeightSpan(textAttributes.getEffectiveLineHeight())));
|
||||
}
|
||||
if (textAttributes.mTextTransform != TextTransform.UNSET && textAttributes.mTextTransform != TextTransform.NONE) {
|
||||
ops.add(
|
||||
new SetSpanOperation(
|
||||
start,
|
||||
end,
|
||||
new CustomTextTransformSpan(textAttributes.mTextTransform)));
|
||||
}
|
||||
|
||||
//TODO: add react tag as part of the fragments, react tag is used on Touch events
|
||||
int reactTag = 1;
|
||||
|
||||
ops.add(new SetSpanOperation(start, end, new ReactTagSpan(reactTag)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static Spannable spannedFromTextFragments(
|
||||
Context context,
|
||||
ReadableArray fragments, String text) {
|
||||
SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
|
||||
// TODO(5837930): Investigate whether it's worth optimizing this part and do it if so
|
||||
|
||||
// The {@link SpannableStringBuilder} implementation require setSpan operation to be called
|
||||
// up-to-bottom, otherwise all the spannables that are withing the region for which one may set
|
||||
// a new spannable will be wiped out
|
||||
List<SetSpanOperation> ops = new ArrayList<>();
|
||||
|
||||
buildSpannedFromShadowNode(context, fragments, sb, ops);
|
||||
|
||||
// TODO: add support for AllowScaling in C++
|
||||
// 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));
|
||||
//
|
||||
// ops.add(new SetSpanOperation(0, sb.length(), new AbsoluteSizeSpan(defaultFontSize)));
|
||||
// }
|
||||
//
|
||||
// textShadowNode.mContainsImages = false;
|
||||
// textShadowNode.mHeightOfTallestInlineImage = Float.NaN;
|
||||
|
||||
// While setting the Spans on the final text, we also check whether any of them are images.
|
||||
int priority = 0;
|
||||
for (SetSpanOperation op : ops) {
|
||||
// TODO: add support for TextInlineImage in C++
|
||||
// if (op.what instanceof TextInlineImageSpan) {
|
||||
// int height = ((TextInlineImageSpan) op.what).getHeight();
|
||||
// textShadowNode.mContainsImages = true;
|
||||
// if (Float.isNaN(textShadowNode.mHeightOfTallestInlineImage)
|
||||
// || height > textShadowNode.mHeightOfTallestInlineImage) {
|
||||
// textShadowNode.mHeightOfTallestInlineImage = height;
|
||||
// }
|
||||
// }
|
||||
|
||||
// Actual order of calling {@code execute} does NOT matter,
|
||||
// but the {@code priority} DOES matter.
|
||||
op.execute(sb, priority);
|
||||
priority++;
|
||||
}
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
public static float[] measureText(
|
||||
ReactContext context,
|
||||
ReactTextView view,
|
||||
ReadableNativeMap attributedString,
|
||||
ReadableNativeMap paragraphAttributes,
|
||||
float width,
|
||||
YogaMeasureMode widthYogaMeasureMode,
|
||||
float height,
|
||||
YogaMeasureMode heightYogaMeasureMode) {
|
||||
|
||||
// TODO(5578671): Handle text direction (see View#getTextDirectionHeuristic)
|
||||
TextPaint textPaint = sTextPaintInstance;
|
||||
Layout layout;
|
||||
|
||||
Spannable preparedSpannableText = view == null ? null : view.getSpanned();
|
||||
|
||||
// TODO add these props to paragraph attributes
|
||||
int textBreakStrategy = Layout.BREAK_STRATEGY_HIGH_QUALITY;
|
||||
boolean includeFontPadding = true;
|
||||
|
||||
if (preparedSpannableText == null) {
|
||||
preparedSpannableText = spannedFromTextFragments(context, attributedString.getArray("fragments"), attributedString.getString("string"));
|
||||
}
|
||||
|
||||
if (preparedSpannableText == null) {
|
||||
throw new IllegalStateException("Spannable element has not been prepared in onBeforeLayout");
|
||||
}
|
||||
Spanned text = preparedSpannableText;
|
||||
BoringLayout.Metrics boring = BoringLayout.isBoring(text, textPaint);
|
||||
float desiredWidth = boring == null ?
|
||||
Layout.getDesiredWidth(text, textPaint) : Float.NaN;
|
||||
|
||||
// technically, width should never be negative, but there is currently a bug in
|
||||
boolean unconstrainedWidth = widthYogaMeasureMode == YogaMeasureMode.UNDEFINED || width < 0;
|
||||
|
||||
if (boring == null &&
|
||||
(unconstrainedWidth ||
|
||||
(!YogaConstants.isUndefined(desiredWidth) && desiredWidth <= width))) {
|
||||
// Is used when the width is not known and the text is not boring, ie. if it contains
|
||||
// unicode characters.
|
||||
|
||||
int hintWidth = (int) Math.ceil(desiredWidth);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
layout = new StaticLayout(
|
||||
text,
|
||||
textPaint,
|
||||
hintWidth,
|
||||
Layout.Alignment.ALIGN_NORMAL,
|
||||
1.f,
|
||||
0.f,
|
||||
includeFontPadding);
|
||||
} else {
|
||||
layout = StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, hintWidth)
|
||||
.setAlignment(Layout.Alignment.ALIGN_NORMAL)
|
||||
.setLineSpacing(0.f, 1.f)
|
||||
.setIncludePad(includeFontPadding)
|
||||
.setBreakStrategy(textBreakStrategy)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
|
||||
.build();
|
||||
}
|
||||
|
||||
} else if (boring != null && (unconstrainedWidth || boring.width <= width)) {
|
||||
// Is used for single-line, boring text when the width is either unknown or bigger
|
||||
// than the width of the text.
|
||||
layout = BoringLayout.make(
|
||||
text,
|
||||
textPaint,
|
||||
boring.width,
|
||||
Layout.Alignment.ALIGN_NORMAL,
|
||||
1.f,
|
||||
0.f,
|
||||
boring,
|
||||
includeFontPadding);
|
||||
} else {
|
||||
// Is used for multiline, boring text and the width is known.
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
layout = new StaticLayout(
|
||||
text,
|
||||
textPaint,
|
||||
(int) width,
|
||||
Layout.Alignment.ALIGN_NORMAL,
|
||||
1.f,
|
||||
0.f,
|
||||
includeFontPadding);
|
||||
} else {
|
||||
layout = StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, (int) width)
|
||||
.setAlignment(Layout.Alignment.ALIGN_NORMAL)
|
||||
.setLineSpacing(0.f, 1.f)
|
||||
.setIncludePad(includeFontPadding)
|
||||
.setBreakStrategy(textBreakStrategy)
|
||||
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
int maximumNumberOfLines = paragraphAttributes.hasKey("maximumNumberOfLines") ? paragraphAttributes.getInt("maximumNumberOfLines") : UNSET;
|
||||
|
||||
width = layout.getWidth();
|
||||
if (maximumNumberOfLines != UNSET
|
||||
&& maximumNumberOfLines != 0
|
||||
&& maximumNumberOfLines < layout.getLineCount()) {
|
||||
height = layout.getLineBottom(maximumNumberOfLines - 1);
|
||||
} else {
|
||||
height = layout.getHeight();
|
||||
}
|
||||
|
||||
return new float[] { PixelUtil.toSPFromPixel(width), PixelUtil.toSPFromPixel(height) };
|
||||
}
|
||||
|
||||
private static class SetSpanOperation {
|
||||
protected int start, end;
|
||||
protected Object what;
|
||||
|
||||
SetSpanOperation(int start, int end, Object what) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.what = what;
|
||||
}
|
||||
|
||||
public void execute(SpannableStringBuilder sb, int priority) {
|
||||
// All spans will automatically extend to the right of the text, but not the left - except
|
||||
// for spans that start at the beginning of the text.
|
||||
int spanFlags = Spannable.SPAN_EXCLUSIVE_INCLUSIVE;
|
||||
if (start == 0) {
|
||||
spanFlags = Spannable.SPAN_INCLUSIVE_INCLUSIVE;
|
||||
}
|
||||
|
||||
spanFlags &= ~Spannable.SPAN_PRIORITY;
|
||||
spanFlags |= (priority << Spannable.SPAN_PRIORITY_SHIFT) & Spannable.SPAN_PRIORITY;
|
||||
|
||||
sb.setSpan(what, start, end, spanFlags);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -67,6 +67,7 @@ rn_xplat_cxx_library(
|
|||
react_native_xplat_target("fabric/graphics:graphics"),
|
||||
react_native_xplat_target("fabric/textlayoutmanager:textlayoutmanager"),
|
||||
react_native_xplat_target("fabric/components/view:view"),
|
||||
react_native_xplat_target("fabric/uimanager:uimanager"),
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <fabric/components/text/ParagraphShadowNode.h>
|
||||
#include <fabric/core/ConcreteComponentDescriptor.h>
|
||||
#include <fabric/textlayoutmanager/TextLayoutManager.h>
|
||||
#include <fabric/uimanager/ContextContainer.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
@ -22,11 +23,11 @@ class ParagraphComponentDescriptor final:
|
|||
|
||||
public:
|
||||
|
||||
ParagraphComponentDescriptor(SharedEventDispatcher eventDispatcher):
|
||||
ParagraphComponentDescriptor(SharedEventDispatcher eventDispatcher, const SharedContextContainer &contextContainer):
|
||||
ConcreteComponentDescriptor<ParagraphShadowNode>(eventDispatcher) {
|
||||
// Every single `ParagraphShadowNode` will have a reference to
|
||||
// a shared `TextLayoutManager`.
|
||||
textLayoutManager_ = std::make_shared<TextLayoutManager>();
|
||||
textLayoutManager_ = std::make_shared<TextLayoutManager>(contextContainer);
|
||||
}
|
||||
|
||||
void adopt(UnsharedShadowNode shadowNode) const override {
|
||||
|
|
|
@ -41,6 +41,7 @@ void ParagraphShadowNode::updateLocalData() {
|
|||
|
||||
Size ParagraphShadowNode::measure(LayoutConstraints layoutConstraints) const {
|
||||
return textLayoutManager_->measure(
|
||||
getTag(),
|
||||
getAttributedString(),
|
||||
getProps()->paragraphAttributes,
|
||||
layoutConstraints
|
||||
|
|
|
@ -7,6 +7,7 @@ load(
|
|||
"get_apple_compiler_flags",
|
||||
"get_apple_inspector_flags",
|
||||
"react_native_xplat_target",
|
||||
"react_native_target",
|
||||
"rn_xplat_cxx_library",
|
||||
"subdir_glob",
|
||||
)
|
||||
|
@ -39,6 +40,9 @@ rn_xplat_cxx_library(
|
|||
"-std=c++14",
|
||||
"-Wall",
|
||||
],
|
||||
fbandroid_deps = [
|
||||
react_native_target("jni/react/jni:jni"),
|
||||
],
|
||||
fbandroid_exported_headers = subdir_glob(
|
||||
[
|
||||
("", "*.h"),
|
||||
|
@ -107,6 +111,7 @@ rn_xplat_cxx_library(
|
|||
react_native_xplat_target("fabric/core:core"),
|
||||
react_native_xplat_target("fabric/debug:debug"),
|
||||
react_native_xplat_target("fabric/graphics:graphics"),
|
||||
react_native_xplat_target("fabric/uimanager:uimanager"),
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
@ -7,12 +7,13 @@
|
|||
|
||||
#include "TextLayoutManager.h"
|
||||
|
||||
#include <react/jni/ReadableNativeMap.h>
|
||||
|
||||
using namespace facebook::jni;
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
TextLayoutManager::TextLayoutManager() {
|
||||
}
|
||||
|
||||
TextLayoutManager::~TextLayoutManager() {
|
||||
}
|
||||
|
||||
|
@ -21,12 +22,28 @@ void *TextLayoutManager::getNativeTextLayoutManager() const {
|
|||
}
|
||||
|
||||
Size TextLayoutManager::measure(
|
||||
Tag reactTag,
|
||||
AttributedString attributedString,
|
||||
ParagraphAttributes paragraphAttributes,
|
||||
LayoutConstraints layoutConstraints
|
||||
) const {
|
||||
// Not implemented.
|
||||
return {};
|
||||
|
||||
const jni::global_ref<jobject> & fabricUIManager = contextContainer_->getInstance<jni::global_ref<jobject>>("FabricUIManager");
|
||||
|
||||
auto clazz = jni::findClassStatic("com/facebook/fbreact/fabricxx/UIManager");
|
||||
static auto measure =
|
||||
clazz->getMethod<JArrayFloat::javaobject(jint, jstring, ReadableNativeMap::javaobject, ReadableNativeMap::javaobject, jint, jint)>("measure");
|
||||
|
||||
int width = (int) layoutConstraints.maximumSize.width;
|
||||
int height = (int) layoutConstraints.maximumSize.height;
|
||||
local_ref<JString> componentName = make_jstring("RCTText");
|
||||
auto values = measure(fabricUIManager, reactTag, componentName.get(), ReadableNativeMap::newObjectCxxArgs(attributedString.toDynamic()).get(), ReadableNativeMap::newObjectCxxArgs(paragraphAttributes.toDynamic()).get(), width, height);
|
||||
|
||||
std::vector<float> indices;
|
||||
indices.resize(values->size());
|
||||
values->getRegion(0, values->size(), indices.data());
|
||||
|
||||
return {(float) indices[0], (float) indices[1]};
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <fabric/attributedstring/AttributedString.h>
|
||||
#include <fabric/attributedstring/ParagraphAttributes.h>
|
||||
#include <fabric/core/LayoutConstraints.h>
|
||||
#include <fabric/uimanager/ContextContainer.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
@ -27,13 +28,14 @@ class TextLayoutManager {
|
|||
|
||||
public:
|
||||
|
||||
TextLayoutManager();
|
||||
TextLayoutManager(const SharedContextContainer &contextContainer) : contextContainer_(contextContainer) {};
|
||||
~TextLayoutManager();
|
||||
|
||||
/*
|
||||
* Measures `attributedString` using native text rendering infrastructure.
|
||||
*/
|
||||
Size measure(
|
||||
Tag reactTag,
|
||||
AttributedString attributedString,
|
||||
ParagraphAttributes paragraphAttributes,
|
||||
LayoutConstraints layoutConstraints
|
||||
|
@ -46,8 +48,10 @@ public:
|
|||
void *getNativeTextLayoutManager() const;
|
||||
|
||||
private:
|
||||
|
||||
|
||||
void *self_;
|
||||
|
||||
SharedContextContainer contextContainer_;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <fabric/attributedstring/AttributedString.h>
|
||||
#include <fabric/attributedstring/ParagraphAttributes.h>
|
||||
#include <fabric/core/LayoutConstraints.h>
|
||||
#include <fabric/uimanager/ContextContainer.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
@ -25,13 +26,14 @@ using SharedTextLayoutManager = std::shared_ptr<const TextLayoutManager>;
|
|||
*/
|
||||
class TextLayoutManager {
|
||||
public:
|
||||
TextLayoutManager();
|
||||
TextLayoutManager(const SharedContextContainer &contextContainer);
|
||||
~TextLayoutManager();
|
||||
|
||||
/*
|
||||
* Measures `attributedString` using native text rendering infrastructure.
|
||||
*/
|
||||
Size measure(
|
||||
Tag reactTag,
|
||||
AttributedString attributedString,
|
||||
ParagraphAttributes paragraphAttributes,
|
||||
LayoutConstraints layoutConstraints
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
TextLayoutManager::TextLayoutManager() {
|
||||
TextLayoutManager::TextLayoutManager(const SharedContextContainer &contextContainer) {
|
||||
self_ = (__bridge_retained void *)[RCTTextLayoutManager new];
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ void *TextLayoutManager::getNativeTextLayoutManager() const {
|
|||
}
|
||||
|
||||
Size TextLayoutManager::measure(
|
||||
Tag reactTag,
|
||||
AttributedString attributedString,
|
||||
ParagraphAttributes paragraphAttributes,
|
||||
LayoutConstraints layoutConstraints
|
||||
|
|
Loading…
Reference in New Issue