Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Substr measurements expensify #56

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, assign) BOOL adjustsFontSizeToFit;
@property (nonatomic, assign) CGFloat minimumFontScale;
@property (nonatomic, copy) RCTDirectEventBlock onTextLayout;
@property (nonatomic, copy) NSArray *textLayoutRegions;

- (void)uiManagerWillPerformMounting;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ @implementation RCTTextViewManager {
RCT_REMAP_SHADOW_PROPERTY(minimumFontScale, minimumFontScale, CGFloat)

RCT_EXPORT_SHADOW_PROPERTY(onTextLayout, RCTDirectEventBlock)
RCT_EXPORT_SHADOW_PROPERTY(textLayoutRegions, NSArray)

RCT_EXPORT_VIEW_PROPERTY(selectable, BOOL)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const textViewConfig = {
minimumFontScale: true,
textBreakStrategy: true,
onTextLayout: true,
textLayoutRegions: true,
onInlineViewLayout: true,
dataDetectorType: true,
android_hyphenationFrequency: true,
Expand Down
5 changes: 5 additions & 0 deletions packages/react-native/Libraries/Text/TextProps.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ export type TextProps = $ReadOnly<{|
onMoveShouldSetResponder?: ?() => boolean,
onTextLayout?: ?(event: TextLayoutEvent) => mixed,

/**
* Regions for text layout tracking
*/
textLayoutRegions?: ?$ReadOnlyArray<$ReadOnlyArray<number>>,

/**
* Defines how far your touch may move off of the button, before
* deactivating the button.
Expand Down
11 changes: 11 additions & 0 deletions packages/react-native/Libraries/Types/CoreEventTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,22 @@ interface TextLayoutLine {
y: number;
}

interface TextLayoutRegion {
text: string;
height: number;
width: number;
x: number;
y: number;
line: number;
region: number;
}

/**
* @see TextProps.onTextLayout
*/
export interface TextLayoutEventData extends TargetedEvent {
lines: TextLayoutLine[];
regions: TextLayoutRegion[];
}

// Similar to React.SyntheticEvent except for nativeEvent
Expand Down
11 changes: 11 additions & 0 deletions packages/react-native/Libraries/Types/CoreEventTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ export type TextLayout = $ReadOnly<{|
xHeight: number,
|}>;

export type TextRegion = $ReadOnly<{|
text: string,
height: number,
width: number,
x: number,
y: number,
line: number,
region: number,
|}>;

export type LayoutEvent = SyntheticEvent<
$ReadOnly<{|
layout: Layout,
Expand All @@ -81,6 +91,7 @@ export type LayoutEvent = SyntheticEvent<
export type TextLayoutEvent = SyntheticEvent<
$ReadOnly<{|
lines: Array<TextLayout>,
regions: Array<TextRegion>,
|}>,
>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.react.common.mapbuffer.MapBuffer;
import com.facebook.react.common.mapbuffer.ReadableMapBuffer;
import com.facebook.react.config.ReactFeatureFlags;
import com.facebook.react.fabric.events.EventBeatManager;
Expand Down Expand Up @@ -443,28 +444,35 @@ public void onCatalystInstanceDestroy() {
}

@SuppressWarnings("unused")
private NativeArray measureLines(
ReadableMap attributedString, ReadableMap paragraphAttributes, float width, float height) {
return (NativeArray)
private NativeMap measureLines(
ReadableMap attributedString,
ReadableMap paragraphAttributes,
float width,
float height,
MapBuffer textLayoutRegions) {
return (NativeMap)
TextLayoutManager.measureLines(
mReactApplicationContext,
attributedString,
paragraphAttributes,
PixelUtil.toPixelFromDIP(width));
PixelUtil.toPixelFromDIP(width),
textLayoutRegions);
}

@SuppressWarnings("unused")
private NativeArray measureLinesMapBuffer(
private NativeMap measureLinesMapBuffer(
ReadableMapBuffer attributedString,
ReadableMapBuffer paragraphAttributes,
float width,
float height) {
return (NativeArray)
float height,
ReadableMapBuffer textLayoutRegions) {
return (NativeMap)
TextLayoutManagerMapBuffer.measureLines(
mReactApplicationContext,
attributedString,
paragraphAttributes,
PixelUtil.toPixelFromDIP(width));
PixelUtil.toPixelFromDIP(width),
textLayoutRegions);
}

@SuppressWarnings("unused")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,22 @@
import android.text.TextPaint;
import android.util.DisplayMetrics;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.mapbuffer.MapBuffer;

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) {
public static WritableMap getFontMetrics(
CharSequence text, Layout layout, TextPaint paint, Context context, MapBuffer textLayoutRegions) {
DisplayMetrics dm = context.getResources().getDisplayMetrics();
WritableArray lines = Arguments.createArray();
WritableArray regions = 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
Expand All @@ -41,6 +44,7 @@ public static WritableArray getFontMetrics(
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);
Expand All @@ -56,8 +60,47 @@ public static WritableArray getFontMetrics(
line.putDouble("xHeight", xHeight);
line.putString(
"text", text.subSequence(layout.getLineStart(i), layout.getLineEnd(i)).toString());

lines.pushMap(line);
}
return lines;

for (int j = 0; j < textLayoutRegions.getCount(); j++) {
for (int i = 0; i < layout.getLineCount(); i++) {
Rect bounds = new Rect();
layout.getLineBounds(i, bounds);

MapBuffer textLayoutRegion = textLayoutRegions.getMapBuffer(j);

int offset = layout.getLineEnd(i) >= textLayoutRegion.getInt(1) ? 0 : 1;
int startIndex = Math.max(layout.getLineStart(i), textLayoutRegion.getInt(0));
int endIndex = Math.min(layout.getLineEnd(i), textLayoutRegion.getInt(1)) - offset;

if (startIndex > endIndex + 1) {
break;
}

Rect regionBounds = new Rect(
(int)layout.getPrimaryHorizontal(startIndex),
bounds.top,
(int)layout.getPrimaryHorizontal(endIndex),
bounds.bottom);

WritableMap region = Arguments.createMap();
region.putDouble("x", regionBounds.left / dm.density);
region.putDouble("y", regionBounds.top / dm.density);
region.putDouble("width", regionBounds.width() / dm.density);
region.putDouble("height", regionBounds.height() / dm.density);
region.putString("text", text.subSequence(startIndex, endIndex).toString());
region.putInt("region", j);
region.putInt("line", i);
regions.pushMap(region);
}
}

WritableMap textLayoutMetrics = Arguments.createMap();
textLayoutMetrics.putArray("lines", lines);
textLayoutMetrics.putArray("regions", regions);

return textLayoutMetrics;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import com.facebook.react.bridge.ReactSoftExceptionLogger;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.mapbuffer.MapBuffer;
import com.facebook.react.uimanager.NativeViewHierarchyOptimizer;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.ReactShadowNode;
Expand Down Expand Up @@ -58,6 +60,7 @@ public class ReactTextShadowNode extends ReactBaseTextShadowNode {
private @Nullable Spannable mPreparedSpannableText;

private boolean mShouldNotifyOnTextLayout;
private MapBuffer mTextLayoutRegions;

private final YogaMeasureFunction mTextMeasureFunction =
new YogaMeasureFunction() {
Expand Down Expand Up @@ -107,15 +110,14 @@ public long measure(

if (mShouldNotifyOnTextLayout) {
ThemedReactContext themedReactContext = getThemedContext();
WritableArray lines =
FontMetricsUtil.getFontMetrics(
text, layout, sTextPaintInstance, themedReactContext);
WritableMap event = Arguments.createMap();
event.putArray("lines", lines);
WritableMap textLayoutMetrics =
FontMetricsUtil.getFontMetrics(
text, layout, sTextPaintInstance, themedReactContext, mTextLayoutRegions);

if (themedReactContext.hasActiveReactInstance()) {
themedReactContext
.getJSModule(RCTEventEmitter.class)
.receiveEvent(getReactTag(), "topTextLayout", event);
.receiveEvent(getReactTag(), "topTextLayout", textLayoutMetrics);
} else {
ReactSoftExceptionLogger.logSoftException(
"ReactTextShadowNode",
Expand Down Expand Up @@ -357,6 +359,11 @@ public void setShouldNotifyOnTextLayout(boolean shouldNotifyOnTextLayout) {
mShouldNotifyOnTextLayout = shouldNotifyOnTextLayout;
}

@ReactProp(name = "textLayoutRegions")
public void setTextLayoutRegions(ReadableArray textLayoutRegions) {
mTextLayoutRegions = (MapBuffer) textLayoutRegions;
}

@Override
public Iterable<? extends ReactShadowNode> calculateLayoutOnChildren() {
// Run flexbox on and return the descendants which are inline views.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableNativeMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.react.common.mapbuffer.MapBuffer;
import com.facebook.react.common.mapbuffer.ReadableMapBuffer;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.ReactStylesDiffMap;
import com.facebook.react.uimanager.ViewProps;
Expand Down Expand Up @@ -564,11 +566,12 @@ public static long measureText(
return YogaMeasureOutput.make(widthInSP, heightInSP);
}

public static WritableArray measureLines(
public static WritableMap measureLines(
@NonNull Context context,
ReadableMap attributedString,
ReadableMap paragraphAttributes,
float width) {
float width,
MapBuffer textLayoutRegions) {
Spannable text = getOrCreateSpannableForText(context, attributedString, null);
BoringLayout.Metrics boring = BoringLayout.isBoring(text, sTextPaintInstance);

Expand All @@ -592,7 +595,7 @@ public static WritableArray measureLines(
includeFontPadding,
textBreakStrategy,
hyphenationFrequency);
return FontMetricsUtil.getFontMetrics(text, layout, sTextPaintInstance, context);
return FontMetricsUtil.getFontMetrics(text, layout, sTextPaintInstance, context, textLayoutRegions);
}

// TODO T31905686: This class should be private
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.ReactNoCrashSoftException;
import com.facebook.react.bridge.ReactSoftExceptionLogger;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.react.common.mapbuffer.MapBuffer;
import com.facebook.react.uimanager.PixelUtil;
Expand Down Expand Up @@ -581,11 +582,12 @@ public static long measureText(
return YogaMeasureOutput.make(widthInSP, heightInSP);
}

public static WritableArray measureLines(
public static WritableMap measureLines(
@NonNull Context context,
MapBuffer attributedString,
MapBuffer paragraphAttributes,
float width) {
float width,
MapBuffer textLayoutRegions) {

Spannable text = getOrCreateSpannableForText(context, attributedString, null);
BoringLayout.Metrics boring = BoringLayout.isBoring(text, sTextPaintInstance);
Expand All @@ -610,7 +612,8 @@ public static WritableArray measureLines(
includeFontPadding,
textBreakStrategy,
hyphenationFrequency);
return FontMetricsUtil.getFontMetrics(text, layout, sTextPaintInstance, context);

return FontMetricsUtil.getFontMetrics(text, layout, sTextPaintInstance, context, textLayoutRegions);
}

// TODO T31905686: This class should be private
Expand Down
Loading