mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-28 20:25:33 +08:00
Custom fonts support added
Reviewed By: andreicoman11 Differential Revision: D2629077 fb-gh-sync-id: 8d647aff13f97d90c5047ad0ddbcae90215ca4ca
This commit is contained in:
committed by
facebook-github-bot-4
parent
1ae7a77934
commit
bfeaa6a4f5
@@ -160,6 +160,22 @@ var TextExample = React.createClass({
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</UIExplorerBlock>
|
</UIExplorerBlock>
|
||||||
|
<UIExplorerBlock title="Custom Fonts">
|
||||||
|
<View style={{flexDirection: 'row', alignItems: 'flex-start'}}>
|
||||||
|
<View style={{flex: 1}}>
|
||||||
|
<Text style={{fontFamily: 'notoserif'}}>
|
||||||
|
NotoSerif Regular
|
||||||
|
</Text>
|
||||||
|
<Text style={{fontFamily: 'notoserif', fontStyle: 'italic', fontWeight: 'bold'}}>
|
||||||
|
NotoSerif Bold Italic
|
||||||
|
</Text>
|
||||||
|
<Text style={{fontFamily: 'notoserif', fontStyle: 'italic'}}>
|
||||||
|
NotoSerif Italic (Missing Font file)
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</UIExplorerBlock>
|
||||||
|
|
||||||
<UIExplorerBlock title="Font Size">
|
<UIExplorerBlock title="Font Size">
|
||||||
<Text style={{fontSize: 23}}>
|
<Text style={{fontSize: 23}}>
|
||||||
Size 23
|
Size 23
|
||||||
|
|||||||
BIN
Examples/UIExplorer/android/app/src/main/assets/fonts/notoserif.ttf
Executable file
BIN
Examples/UIExplorer/android/app/src/main/assets/fonts/notoserif.ttf
Executable file
Binary file not shown.
BIN
Examples/UIExplorer/android/app/src/main/assets/fonts/notoserif_bold_italic.ttf
Executable file
BIN
Examples/UIExplorer/android/app/src/main/assets/fonts/notoserif_bold_italic.ttf
Executable file
Binary file not shown.
@@ -14,6 +14,7 @@ import javax.annotation.Nullable;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import android.content.res.AssetManager;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.text.TextPaint;
|
import android.text.TextPaint;
|
||||||
@@ -21,33 +22,43 @@ import android.text.style.MetricAffectingSpan;
|
|||||||
|
|
||||||
public class CustomStyleSpan extends 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
|
* A {@link MetricAffectingSpan} that allows to change the style of the displayed font.
|
||||||
// Typeface.create(Typeface, style) post API 16, but for that you already need a Typeface.
|
* CustomStyleSpan will try to load the fontFamily with the right style and weight from the
|
||||||
// Therefore, here we cache one style for each font family, and let Typeface cache all styles for
|
* assets. The custom fonts will have to be located in the res/assets folder of the application.
|
||||||
// that font family. Of course this is not ideal, and especially after adding Typeface loading
|
* The supported custom fonts extensions are .ttf and .otf. For each font family the bold,
|
||||||
// from assets, we will need to have our own caching mechanism for all Typeface creation types.
|
* italic and bold_italic variants are supported. Given a "family" font family the files in the
|
||||||
// TODO: t6866343 add better Typeface caching
|
* assets/fonts folder need to be family.ttf(.otf) family_bold.ttf(.otf) family_italic.ttf(.otf)
|
||||||
private static final Map<String, Typeface> sTypefaceCache = new HashMap<String, Typeface>();
|
* and family_bold_italic.ttf(.otf). If the right font is not found in the assets folder
|
||||||
|
* CustomStyleSpan will fallback on the most appropriate default typeface depending on the style.
|
||||||
|
* Fonts are retrieved and cached using the {@link ReactFontManager}
|
||||||
|
*/
|
||||||
|
|
||||||
|
private final AssetManager mAssetManager;
|
||||||
|
|
||||||
private final int mStyle;
|
private final int mStyle;
|
||||||
private final int mWeight;
|
private final int mWeight;
|
||||||
private final @Nullable String mFontFamily;
|
private final @Nullable String mFontFamily;
|
||||||
|
|
||||||
public CustomStyleSpan(int fontStyle, int fontWeight, @Nullable String fontFamily) {
|
public CustomStyleSpan(
|
||||||
|
int fontStyle,
|
||||||
|
int fontWeight,
|
||||||
|
@Nullable String fontFamily,
|
||||||
|
AssetManager assetManager) {
|
||||||
mStyle = fontStyle;
|
mStyle = fontStyle;
|
||||||
mWeight = fontWeight;
|
mWeight = fontWeight;
|
||||||
mFontFamily = fontFamily;
|
mFontFamily = fontFamily;
|
||||||
|
mAssetManager = assetManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateDrawState(TextPaint ds) {
|
public void updateDrawState(TextPaint ds) {
|
||||||
apply(ds, mStyle, mWeight, mFontFamily);
|
apply(ds, mStyle, mWeight, mFontFamily, mAssetManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateMeasureState(TextPaint paint) {
|
public void updateMeasureState(TextPaint paint) {
|
||||||
apply(paint, mStyle, mWeight, mFontFamily);
|
apply(paint, mStyle, mWeight, mFontFamily, mAssetManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,7 +72,7 @@ public class CustomStyleSpan extends MetricAffectingSpan {
|
|||||||
* Returns {@link Typeface#NORMAL} or {@link Typeface#BOLD}.
|
* Returns {@link Typeface#NORMAL} or {@link Typeface#BOLD}.
|
||||||
*/
|
*/
|
||||||
public int getWeight() {
|
public int getWeight() {
|
||||||
return (mWeight == ReactTextShadowNode.UNSET ? 0 : mWeight);
|
return (mWeight == ReactTextShadowNode.UNSET ? 0 : mWeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,7 +82,12 @@ public class CustomStyleSpan extends MetricAffectingSpan {
|
|||||||
return mFontFamily;
|
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,
|
||||||
|
AssetManager assetManager) {
|
||||||
int oldStyle;
|
int oldStyle;
|
||||||
Typeface typeface = paint.getTypeface();
|
Typeface typeface = paint.getTypeface();
|
||||||
if (typeface == null) {
|
if (typeface == null) {
|
||||||
@@ -92,23 +108,14 @@ public class CustomStyleSpan extends MetricAffectingSpan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (family != null) {
|
if (family != null) {
|
||||||
typeface = getOrCreateTypeface(family, want);
|
typeface = ReactFontManager.getInstance().getTypeface(family, want, assetManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeface != null) {
|
if (typeface != null) {
|
||||||
paint.setTypeface(Typeface.create(typeface, want));
|
paint.setTypeface(typeface);
|
||||||
} else {
|
} else {
|
||||||
paint.setTypeface(Typeface.defaultFromStyle(want));
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc. All rights reserved.
|
||||||
|
* <p/>
|
||||||
|
* This source code is licensed under the BSD-style license found in the LICENSE file in the root
|
||||||
|
* directory of this source tree. An additional grant of patent rights can be found in the PATENTS
|
||||||
|
* file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.react.views.text;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import android.content.res.AssetManager;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class responsible to load and cache Typeface objects. It will first try to load typefaces inside
|
||||||
|
* the assets/fonts folder and if it doesn't find the right Typeface in that folder will fall back
|
||||||
|
* on the best matching system Typeface The supported custom fonts extensions are .ttf and .otf. For
|
||||||
|
* each font family the bold, italic and bold_italic variants are supported. Given a "family" font
|
||||||
|
* family the files in the assets/fonts folder need to be family.ttf(.otf) family_bold.ttf(.otf)
|
||||||
|
* family_italic.ttf(.otf) and family_bold_italic.ttf(.otf)
|
||||||
|
*/
|
||||||
|
public class ReactFontManager {
|
||||||
|
|
||||||
|
private static final String[] EXTENSIONS = {
|
||||||
|
"",
|
||||||
|
"_bold",
|
||||||
|
"_italic",
|
||||||
|
"_bold_italic"};
|
||||||
|
private static final String[] FILE_EXTENSIONS = {".ttf", ".otf"};
|
||||||
|
private static final String FONTS_ASSET_PATH = "fonts/";
|
||||||
|
|
||||||
|
private static ReactFontManager sReactFontManagerInstance;
|
||||||
|
|
||||||
|
private Map<String, FontFamily> mFontCache;
|
||||||
|
|
||||||
|
private ReactFontManager() {
|
||||||
|
mFontCache = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ReactFontManager getInstance() {
|
||||||
|
if (sReactFontManagerInstance == null) {
|
||||||
|
sReactFontManagerInstance = new ReactFontManager();
|
||||||
|
}
|
||||||
|
return sReactFontManagerInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
@Nullable Typeface getTypeface(
|
||||||
|
String fontFamilyName,
|
||||||
|
int style,
|
||||||
|
AssetManager assetManager) {
|
||||||
|
FontFamily fontFamily = mFontCache.get(fontFamilyName);
|
||||||
|
if (fontFamily == null) {
|
||||||
|
fontFamily = new FontFamily();
|
||||||
|
mFontCache.put(fontFamilyName, fontFamily);
|
||||||
|
}
|
||||||
|
|
||||||
|
Typeface typeface = fontFamily.getTypeface(style);
|
||||||
|
if (typeface == null) {
|
||||||
|
typeface = createTypeface(fontFamilyName, style, assetManager);
|
||||||
|
if (typeface != null) {
|
||||||
|
fontFamily.setTypeface(style, typeface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeface;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static
|
||||||
|
@Nullable Typeface createTypeface(
|
||||||
|
String fontFamilyName,
|
||||||
|
int style,
|
||||||
|
AssetManager assetManager) {
|
||||||
|
String extension = EXTENSIONS[style];
|
||||||
|
for (String fileExtension : FILE_EXTENSIONS) {
|
||||||
|
String fileName = new StringBuilder()
|
||||||
|
.append(FONTS_ASSET_PATH)
|
||||||
|
.append(fontFamilyName)
|
||||||
|
.append(extension)
|
||||||
|
.append(fileExtension)
|
||||||
|
.toString();
|
||||||
|
try {
|
||||||
|
return Typeface.createFromAsset(assetManager, fileName);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
// unfortunately Typeface.createFromAsset throws an exception instead of returning null
|
||||||
|
// if the typeface doesn't exist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Typeface.create(fontFamilyName, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FontFamily {
|
||||||
|
|
||||||
|
private SparseArray<Typeface> mTypefaceSparseArray;
|
||||||
|
|
||||||
|
private FontFamily() {
|
||||||
|
mTypefaceSparseArray = new SparseArray<>(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Typeface getTypeface(int style) {
|
||||||
|
return mTypefaceSparseArray.get(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTypeface(int style, Typeface typeface) {
|
||||||
|
mTypefaceSparseArray.put(style, typeface);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,16 +43,16 @@ import com.facebook.react.uimanager.ViewProps;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link ReactShadowNode} class for spannable text view.
|
* {@link ReactShadowNode} class for spannable text view.
|
||||||
*
|
* <p/>
|
||||||
* This node calculates {@link Spannable} based on subnodes of the same type and passes the
|
* This node calculates {@link Spannable} based on subnodes of the same type and passes the
|
||||||
* resulting object down to textview's shadowview and actual native {@link TextView} instance.
|
* resulting object down to textview's shadowview and actual native {@link TextView} instance. It is
|
||||||
* It is important to keep in mind that {@link Spannable} is calculated only on layout step, so if
|
* important to keep in mind that {@link Spannable} is calculated only on layout step, so if there
|
||||||
* there are any text properties that may/should affect the result of {@link Spannable} they should
|
* are any text properties that may/should affect the result of {@link Spannable} they should be set
|
||||||
* be set in a corresponding {@link ReactTextShadowNode}. Resulting {@link Spannable} object is then
|
* in a corresponding {@link ReactTextShadowNode}. Resulting {@link Spannable} object is then then
|
||||||
* then passed as "computedDataFromMeasure" down to shadow and native view.
|
* passed as "computedDataFromMeasure" down to shadow and native view.
|
||||||
*
|
* <p/>
|
||||||
* TODO(7255858): Rename *CSSNode to *ShadowView (or sth similar) as it's no longer is used
|
* TODO(7255858): Rename *CSSNode to *ShadowView (or sth similar) as it's no longer is used solely
|
||||||
* solely for layouting
|
* for layouting
|
||||||
*/
|
*/
|
||||||
public class ReactTextShadowNode extends LayoutShadowNode {
|
public class ReactTextShadowNode extends LayoutShadowNode {
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ public class ReactTextShadowNode extends LayoutShadowNode {
|
|||||||
buildSpannedFromTextCSSNode((ReactTextShadowNode) child, sb, ops);
|
buildSpannedFromTextCSSNode((ReactTextShadowNode) child, sb, ops);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalViewOperationException("Unexpected view type nested under text node: "
|
throw new IllegalViewOperationException("Unexpected view type nested under text node: "
|
||||||
+ child.getClass());
|
+ child.getClass());
|
||||||
}
|
}
|
||||||
((ReactTextShadowNode) child).markUpdateSeen();
|
((ReactTextShadowNode) child).markUpdateSeen();
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,8 @@ public class ReactTextShadowNode extends LayoutShadowNode {
|
|||||||
new CustomStyleSpan(
|
new CustomStyleSpan(
|
||||||
textCSSNode.mFontStyle,
|
textCSSNode.mFontStyle,
|
||||||
textCSSNode.mFontWeight,
|
textCSSNode.mFontWeight,
|
||||||
textCSSNode.mFontFamily)));
|
textCSSNode.mFontFamily,
|
||||||
|
textCSSNode.getThemedContext().getAssets())));
|
||||||
}
|
}
|
||||||
ops.add(new SetSpanOperation(start, end, new ReactTagSpan(textCSSNode.getReactTag())));
|
ops.add(new SetSpanOperation(start, end, new ReactTagSpan(textCSSNode.getReactTag())));
|
||||||
}
|
}
|
||||||
@@ -197,7 +198,7 @@ public class ReactTextShadowNode extends LayoutShadowNode {
|
|||||||
0,
|
0,
|
||||||
boring,
|
boring,
|
||||||
true);
|
true);
|
||||||
} else {
|
} else {
|
||||||
// Is used for multiline, boring text and the width is known.
|
// Is used for multiline, boring text and the width is known.
|
||||||
layout = new StaticLayout(
|
layout = new StaticLayout(
|
||||||
text,
|
text,
|
||||||
|
|||||||
Reference in New Issue
Block a user