Skip to content

Commit

Permalink
Android: Send <Text> metrics in onTextLayout events
Browse files Browse the repository at this point in the history
Summary:
@public
As we're doing in D9440914 (OSS 64a5253), send text metrics in an onTextLayout callback. These can be used by surrounding views for doing complicated layout like:
- displaying a cursor at the end of text
- vertical centering using capheight-baseline

This right now isn't very performant but is only done when `onTextLayout` is set. I plan to optimize it with a capheight and xheight cache in a follow up diff.

Reviewed By: achen1

Differential Revision: D9585613

fbshipit-source-id: aa20535b8371d5aecf15822d66a0d973c9a7eeda
  • Loading branch information
Mehdi Mulani authored and facebook-github-bot committed Sep 12, 2018
1 parent 36199d3 commit 737f937
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright (c) 2015-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.content.Context;
import android.graphics.Rect;
import android.text.Layout;
import android.text.TextPaint;
import android.util.DisplayMetrics;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;

public class FontMetricsUtil {
public static WritableArray getFontMetrics(CharSequence text, Layout layout, TextPaint paint, Context context) {
DisplayMetrics dm = context.getResources().getDisplayMetrics();

This comment has been minimized.

Copy link
@dblachut-adb

dblachut-adb Apr 19, 2019

Shouldn't it use DisplayMetricsHolder?

WritableArray lines = Arguments.createArray();
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("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.putString(
"text", text.subSequence(layout.getLineStart(i), layout.getLineEnd(i)).toString());
lines.pushMap(line);
}
return lines;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,27 @@

package com.facebook.react.views.text;

import android.graphics.Rect;
import android.os.Build;
import android.text.BoringLayout;
import android.text.Layout;
import android.text.Spannable;
import android.text.Spanned;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.widget.TextView;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.ReactShadowNodeImpl;
import com.facebook.react.uimanager.Spacing;
import com.facebook.react.uimanager.UIViewOperationQueue;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.facebook.yoga.YogaConstants;
import com.facebook.yoga.YogaDirection;
import com.facebook.yoga.YogaMeasureFunction;
Expand All @@ -44,6 +51,8 @@ public class ReactTextShadowNode extends ReactBaseTextShadowNode {

private @Nullable Spannable mPreparedSpannableText;

private boolean mShouldNotifyOnTextLayout;

private final YogaMeasureFunction mTextMeasureFunction =
new YogaMeasureFunction() {
@Override
Expand Down Expand Up @@ -127,11 +136,18 @@ public long measure(
}
}

if (mNumberOfLines != UNSET &&
mNumberOfLines < layout.getLineCount()) {
return YogaMeasureOutput.make(
layout.getWidth(),
layout.getLineBottom(mNumberOfLines - 1));
if (mShouldNotifyOnTextLayout) {
WritableArray lines =
FontMetricsUtil.getFontMetrics(text, layout, sTextPaintInstance, getThemedContext());
WritableMap event = Arguments.createMap();
event.putArray("lines", lines);
getThemedContext()
.getJSModule(RCTEventEmitter.class)
.receiveEvent(getReactTag(), "topTextLayout", event);
}

if (mNumberOfLines != UNSET && mNumberOfLines < layout.getLineCount()) {
return YogaMeasureOutput.make(layout.getWidth(), layout.getLineBottom(mNumberOfLines - 1));
} else {
return YogaMeasureOutput.make(layout.getWidth(), layout.getHeight());
}
Expand Down Expand Up @@ -223,4 +239,9 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {
uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate);
}
}

@ReactProp(name = "onTextLayout")
public void setShouldNotifyOnTextLayout(boolean shouldNotifyOnTextLayout) {
mShouldNotifyOnTextLayout = shouldNotifyOnTextLayout;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
package com.facebook.react.views.text;

import android.text.Spannable;
import com.facebook.react.common.MapBuilder;
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;

/**
* Concrete class for {@link ReactTextAnchorViewManager} which represents view managers of anchor
Expand Down Expand Up @@ -58,4 +61,9 @@ protected void onAfterUpdateTransaction(ReactTextView view) {
super.onAfterUpdateTransaction(view);
view.updateView();
}

@Override
public @Nullable Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of("topTextLayout", MapBuilder.of("registrationName", "onTextLayout"));
}
}

0 comments on commit 737f937

Please sign in to comment.