Skip to content
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
5 changes: 5 additions & 0 deletions Examples/UIExplorer/TextExample.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ var TextExample = React.createClass({
</View>
</View>
</UIExplorerBlock>
<UIExplorerBlock title="Custom Font">
<Text style={{fontFamily: 'DancingScript'}}>
Custom font loaded from an application asset TTF file
</Text>
</UIExplorerBlock>
<UIExplorerBlock title="Font Size">
<Text style={{fontSize: 23}}>
Size 23
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package com.facebook.react.uiapp;

import android.app.Activity;
import android.graphics.Typeface;
import android.os.Bundle;
import android.view.KeyEvent;

Expand All @@ -40,6 +41,7 @@ protected void onCreate(Bundle savedInstanceState) {
.addPackage(new MainReactPackage())
.setUseDeveloperSupport(true)
.setInitialLifecycleState(LifecycleState.RESUMED)
.addTypeFace("DancingScript", Typeface.createFromAsset(getAssets(), "DancingScript-Regular.ttf"))
.build();

((ReactRootView) findViewById(R.id.react_root_view))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
Expand Down Expand Up @@ -52,6 +53,8 @@
import com.facebook.react.uimanager.ReactNative;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.views.text.TypefaceProvider;
import com.facebook.react.views.text.TypefaceProviderImpl;
import com.facebook.soloader.SoLoader;

/**
Expand Down Expand Up @@ -91,6 +94,7 @@ public class ReactInstanceManager {
private @Nullable DefaultHardwareBackBtnHandler mDefaultBackButtonImpl;
private String mSourceUrl;
private @Nullable Activity mCurrentActivity;
private final TypefaceProvider mTypefaceProvider;

private final ReactInstanceDevCommandsHandler mDevInterface =
new ReactInstanceDevCommandsHandler() {
Expand Down Expand Up @@ -185,7 +189,8 @@ private ReactInstanceManager(
List<ReactPackage> packages,
boolean useDeveloperSupport,
@Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener,
LifecycleState initialLifecycleState) {
LifecycleState initialLifecycleState,
TypefaceProvider typefaceProvider) {
initializeSoLoaderIfNecessary(applicationContext);

mApplicationContext = applicationContext;
Expand All @@ -204,6 +209,7 @@ private ReactInstanceManager(
useDeveloperSupport);
mBridgeIdleDebugListener = bridgeIdleDebugListener;
mLifecycleState = initialLifecycleState;
mTypefaceProvider = typefaceProvider;
}

public DevSupportManager getDevSupportManager() {
Expand Down Expand Up @@ -544,6 +550,7 @@ private ReactApplicationContext createReactContext(
if (mUseDeveloperSupport) {
reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager);
}
reactContext.initializeTypefaceProvider(mTypefaceProvider);

CoreModulesPackage coreModulesPackage =
new CoreModulesPackage(this, mBackBtnHandler);
Expand Down Expand Up @@ -605,6 +612,7 @@ public static class Builder {
private @Nullable Application mApplication;
private boolean mUseDeveloperSupport;
private @Nullable LifecycleState mInitialLifecycleState;
private TypefaceProviderImpl mTypefaceProvider = new TypefaceProviderImpl();

private Builder() {
}
Expand Down Expand Up @@ -679,6 +687,16 @@ public Builder setInitialLifecycleState(LifecycleState initialLifecycleState) {
return this;
}

/**
* Makes a custom typeface available to the React application, under the provided font family
* name. Named fonts provided via this method will be preferred over the standard fonts in the
* event of a naming conflict.
*/
public Builder addTypeFace(String fontFamily, Typeface typeface) {
mTypefaceProvider.addTypeFace(fontFamily, typeface);
return this;
}

/**
* Instantiates a new {@link ReactInstanceManager}.
* Before calling {@code build}, the following must be called:
Expand All @@ -705,7 +723,8 @@ public ReactInstanceManager build() {
mPackages,
mUseDeveloperSupport,
mBridgeIdleDebugListener,
Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"));
Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"),
mTypefaceProvider);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.queue.CatalystQueueConfiguration;
import com.facebook.react.bridge.queue.MessageQueueThread;
import com.facebook.react.views.text.TypefaceProvider;
import com.facebook.react.views.text.TypefaceProviderImpl;
import com.facebook.infer.annotation.Assertions;

/**
* Abstract ContextWrapper for Android applicaiton or activity {@link Context} and
Expand All @@ -42,11 +45,26 @@ public class ReactContext extends ContextWrapper {
private @Nullable MessageQueueThread mJSMessageQueueThread;
private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
private @Nullable Activity mCurrentActivity;
private @Nullable TypefaceProvider mTypefaceProvider;

public ReactContext(Context base) {
super(base);
}

/**
* Set typeface provider for this Context. This should be called exactly once.
*/
public void initializeTypefaceProvider(TypefaceProvider typefaceProvider) {
if (mTypefaceProvider != null) {
throw new IllegalStateException("ReactContext has been already initialized");
}
if (typefaceProvider == null) {
// Create a default typeface provider; no custom typefaces will be available
typefaceProvider = new TypefaceProviderImpl();
}
mTypefaceProvider = typefaceProvider;
}

/**
* Set and initialize CatalystInstance for this Context. This should be called exactly once.
*/
Expand Down Expand Up @@ -130,6 +148,11 @@ public void removeActivityEventListener(ActivityEventListener listener) {
mActivityEventListeners.remove(listener);
}

public TypefaceProvider getTypefaceProvider() {
Assertions.assertNotNull(mTypefaceProvider, "Typeface provider must be initialized");
return mTypefaceProvider;
}

/**
* Should be called by the hosting Fragment in {@link Fragment#onResume}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,14 @@ public List<ViewManager> createViewManagers(ReactApplicationContext reactContext
new ReactHorizontalScrollViewManager(),
new ReactImageManager(),
new ReactProgressBarViewManager(),
new ReactRawTextManager(),
new ReactRawTextManager(reactContext.getTypefaceProvider()),
new ReactScrollViewManager(),
new ReactSwitchManager(),
new ReactTextInputManager(),
new ReactTextViewManager(),
new ReactTextInputManager(reactContext.getTypefaceProvider()),
new ReactTextViewManager(reactContext.getTypefaceProvider()),
new ReactToolbarManager(),
new ReactViewManager(),
new ReactViewPagerManager(),
new ReactVirtualTextViewManager());
new ReactVirtualTextViewManager(reactContext.getTypefaceProvider()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class ThemedReactContext extends ReactContext {

public ThemedReactContext(ReactApplicationContext reactApplicationContext, Context base) {
super(base);
initializeTypefaceProvider(reactApplicationContext.getTypefaceProvider());
initializeWithInstance(reactApplicationContext.getCatalystInstance());
mReactApplicationContext = reactApplicationContext;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,43 +11,32 @@

import javax.annotation.Nullable;

import java.util.HashMap;
import java.util.Map;

import android.graphics.Paint;
import android.graphics.Typeface;
import android.text.TextPaint;
import android.text.style.MetricAffectingSpan;

public class CustomStyleSpan extends MetricAffectingSpan {

// Typeface caching is a bit weird: once a Typeface is created, it cannot be changed, so we need
// to cache each font family and each style that they have. Typeface does cache this already in
// Typeface.create(Typeface, style) post API 16, but for that you already need a Typeface.
// Therefore, here we cache one style for each font family, and let Typeface cache all styles for
// that font family. Of course this is not ideal, and especially after adding Typeface loading
// from assets, we will need to have our own caching mechanism for all Typeface creation types.
// TODO: t6866343 add better Typeface caching
private static final Map<String, Typeface> sTypefaceCache = new HashMap<String, Typeface>();

private final TypefaceProvider mTypefaceProvider;
private final int mStyle;
private final int mWeight;
private final @Nullable String mFontFamily;

public CustomStyleSpan(int fontStyle, int fontWeight, @Nullable String fontFamily) {
public CustomStyleSpan(TypefaceProvider typefaceProvider, int fontStyle, int fontWeight, @Nullable String fontFamily) {
mTypefaceProvider = typefaceProvider;
mStyle = fontStyle;
mWeight = fontWeight;
mFontFamily = fontFamily;
}

@Override
public void updateDrawState(TextPaint ds) {
apply(ds, mStyle, mWeight, mFontFamily);
apply(ds, mStyle, mWeight, mFontFamily, mTypefaceProvider);
}

@Override
public void updateMeasureState(TextPaint paint) {
apply(paint, mStyle, mWeight, mFontFamily);
apply(paint, mStyle, mWeight, mFontFamily, mTypefaceProvider);
}

/**
Expand All @@ -71,7 +60,7 @@ public int getWeight() {
return mFontFamily;
}

private static void apply(Paint paint, int style, int weight, @Nullable String family) {
private static void apply(Paint paint, int style, int weight, @Nullable String family, TypefaceProvider typefaceProvider) {
int oldStyle;
Typeface typeface = paint.getTypeface();
if (typeface == null) {
Expand All @@ -92,7 +81,7 @@ private static void apply(Paint paint, int style, int weight, @Nullable String f
}

if (family != null) {
typeface = getOrCreateTypeface(family, want);
typeface = typefaceProvider.getOrCreateTypeface(family, want);
}

if (typeface != null) {
Expand All @@ -101,14 +90,4 @@ private static void apply(Paint paint, int style, int weight, @Nullable String f
paint.setTypeface(Typeface.defaultFromStyle(want));
}
}

private static Typeface getOrCreateTypeface(String family, int style) {
if (sTypefaceCache.get(family) != null) {
return sTypefaceCache.get(family);
}

Typeface typeface = Typeface.create(family, style);
sTypefaceCache.put(family, typeface);
return typeface;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public class ReactRawTextManager extends ReactTextViewManager {
@VisibleForTesting
public static final String REACT_CLASS = "RCTRawText";

public ReactRawTextManager(TypefaceProvider typefaceProvider) {
super(typefaceProvider);
}

@Override
public String getName() {
return REACT_CLASS;
Expand All @@ -38,7 +42,7 @@ public void updateExtraData(ReactTextView view, Object extraData) {

@Override
public ReactTextShadowNode createShadowNodeInstance() {
return new ReactTextShadowNode(true);
return new ReactTextShadowNode(true, mTypefaceProvider);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public void execute(SpannableStringBuilder sb) {
}

private static final void buildSpannedFromTextCSSNode(
TypefaceProvider typefaceProvider,
ReactTextShadowNode textCSSNode,
SpannableStringBuilder sb,
List<SetSpanOperation> ops) {
Expand All @@ -97,7 +98,7 @@ private static final void buildSpannedFromTextCSSNode(
for (int i = 0, length = textCSSNode.getChildCount(); i < length; i++) {
CSSNode child = textCSSNode.getChildAt(i);
if (child instanceof ReactTextShadowNode) {
buildSpannedFromTextCSSNode((ReactTextShadowNode) child, sb, ops);
buildSpannedFromTextCSSNode(typefaceProvider, (ReactTextShadowNode) child, sb, ops);
} else {
throw new IllegalViewOperationException("Unexpected view type nested under text node: "
+ child.getClass());
Expand Down Expand Up @@ -126,6 +127,7 @@ private static final void buildSpannedFromTextCSSNode(
start,
end,
new CustomStyleSpan(
typefaceProvider,
textCSSNode.mFontStyle,
textCSSNode.mFontWeight,
textCSSNode.mFontFamily)));
Expand All @@ -142,7 +144,7 @@ protected static final Spanned fromTextCSSNode(ReactTextShadowNode textCSSNode)
// 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<SetSpanOperation>();
buildSpannedFromTextCSSNode(textCSSNode, sb, ops);
buildSpannedFromTextCSSNode(textCSSNode.mTypefaceProvider, textCSSNode, sb, ops);
if (textCSSNode.mFontSize == UNSET) {
sb.setSpan(
new AbsoluteSizeSpan((int) Math.ceil(PixelUtil.toPixelFromSP(ViewDefaults.FONT_SIZE_SP))),
Expand Down Expand Up @@ -273,6 +275,7 @@ private static int parseNumericFontWeight(String fontWeightString) {

private @Nullable Spanned mPreparedSpannedText;
private final boolean mIsVirtual;
private final TypefaceProvider mTypefaceProvider;

@Override
public void onBeforeLayout() {
Expand Down Expand Up @@ -398,8 +401,9 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {
}
}

public ReactTextShadowNode(boolean isVirtual) {
public ReactTextShadowNode(boolean isVirtual, TypefaceProvider typefaceProvider) {
mIsVirtual = isVirtual;
mTypefaceProvider = typefaceProvider;
if (!isVirtual) {
setMeasureFunction(TEXT_MEASURE_FUNCTION);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ public class ReactTextViewManager extends BaseViewManager<ReactTextView, ReactTe
@VisibleForTesting
public static final String REACT_CLASS = "RCTText";

protected final TypefaceProvider mTypefaceProvider;

public ReactTextViewManager(TypefaceProvider typefaceProvider) {
mTypefaceProvider = typefaceProvider;
}

@Override
public String getName() {
return REACT_CLASS;
Expand Down Expand Up @@ -87,7 +93,7 @@ public void updateExtraData(ReactTextView view, Object extraData) {

@Override
public ReactTextShadowNode createShadowNodeInstance() {
return new ReactTextShadowNode(false);
return new ReactTextShadowNode(false, mTypefaceProvider);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ public class ReactVirtualTextViewManager extends ReactRawTextManager {
@VisibleForTesting
public static final String REACT_CLASS = "RCTVirtualText";

public ReactVirtualTextViewManager(TypefaceProvider typefaceProvider) {
super(typefaceProvider);
}

@Override
public String getName() {
return REACT_CLASS;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.facebook.react.views.text;

import android.graphics.Typeface;

public interface TypefaceProvider {
Typeface getOrCreateTypeface(String family, int style);
}
Loading