React-Native WebView動態加載字體

背景

使用react-native構建的iOS/Android雙端APP,經過WebView加載本地頁面,須要根據服務器提供的字體列表實現下載和動態加載。javascript

本地字體檢查

有些字體手機操做系統已經提供了,能夠不須要下載和加載。css

iOS

UIFont.familyNames提供了全部系統自帶字體的familyNames,直接將結果返回給RN處理便可。java

SHMFontsModul.h

//
//  SHMFontsModule.h
//  shimo
//
//  Created by Rex Rao on 2018/1/9.
//  Copyright © 2018年 shimo.im. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>

@interface SHMFontsModule : NSObject <RCTBridgeModule>

@end

SHMFontsModule.m

//
//  SHMFontsModule.m
//  shimo
//
//  Created by Rex Rao on 2018/1/9.
//  Copyright © 2018年 shimo.im. All rights reserved.
//

#import "SHMFontsModule.h"
#import <UIKit/UIKit.h>

@implementation SHMFontsModule

RCT_EXPORT_MODULE(SHMFonts);

RCT_REMAP_METHOD(fontFamilyNames,
                 resolver
                 : (RCTPromiseResolveBlock)resolve
                     rejecter
                 : (RCTPromiseRejectBlock)reject) {
    resolve(UIFont.familyNames);
}

@end

Android

安卓系統沒有直接提供接口返回系統字體列表,通過調研和閱讀源代碼,發現有一個類中的私有靜態變量存儲了字體信息,反射便可獲得。但由於Android版本緣由,低版本系統代碼不一樣沒法經過此方法獲得。繼續對這個靜態變量順藤摸瓜,發現Android經過解析字體xml文件來設置此變量的值,根據系統不一樣,字體配置xml文件的位置和結構也有所不一樣。react

  • Android 5.1及如下
    • 路徑:/system/etc/system_fonts.xml
    • 結構樣例請直接查看源文件
  • Android 5.1以上
    • 路徑:/system/etc/fonts.xml
    • 結構樣例請直接查看源文件

Android源碼中有個FontListParser類用來解析此字體配置文件,咱們能夠參考此類完成本身的parser,分兩種配置路徑和結構獲取系統的Font Families,而後傳給RN處理。android

FontListParser.java

package chuxin.shimo.shimowendang.fonts;

import android.util.Xml;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by sohobloo on 2018/1/17.
 */

/**
 * Parser for font config files.
 *
 */
public class FontListParser {

    public static List<String> parse(InputStream in) throws XmlPullParserException, IOException {
        List<String> familyNames = new ArrayList<String>();
        try {
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(in, null);
            parser.nextTag();
            parser.require(XmlPullParser.START_TAG, null, "familyset");
            while (parser.next() != XmlPullParser.END_TAG) {
                if (parser.getEventType() != XmlPullParser.START_TAG) {
                    continue;
                }
                String tag = parser.getName();
                switch (tag) {
                    case "family": {
                        String name = parser.getAttributeValue(null, "name");
                        if (name != null && !name.isEmpty()) {
                            familyNames.add(name);
                            skip(parser);
                        } else {
                            while (parser.next() != XmlPullParser.END_TAG) {
                                if (parser.getEventType() != XmlPullParser.START_TAG) {
                                    continue;
                                }
                                tag = parser.getName();
                                if (tag.equals("nameset")) {
                                    while (parser.next() != XmlPullParser.END_TAG) {
                                        if (parser.getEventType() != XmlPullParser.START_TAG) {
                                            continue;
                                        }
                                        tag = parser.getName();
                                        if (tag.equals("name")) {
                                            name = parser.nextText();
                                            if (name != null && !name.isEmpty()) {
                                                familyNames.add(name);
                                            }
                                        } else {
                                            skip(parser);
                                        }
                                    }
                                } else {
                                    skip(parser);
                                }
                            }
                        }
                        break;
                    }
                    case "alias": {
                        String name = parser.getAttributeValue(null, "name");
                        if (name != null && !name.isEmpty()) {
                            familyNames.add(name);
                        }
                        skip(parser);
                        break;
                    }
                    default:
                        skip(parser);
                        break;
                }
            }
        } finally {
            in.close();
        }

        return familyNames;
    }

    private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
        int depth = 1;
        while (depth > 0) {
            switch (parser.next()) {
                case XmlPullParser.START_TAG:
                    depth++;
                    break;
                case XmlPullParser.END_TAG:
                    depth--;
                    break;
                default:
                    break;
            }
        }
    }
}

FontsModule.java

package chuxin.shimo.shimowendang.fonts;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableArray;

import org.xmlpull.v1.XmlPullParserException;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;

/**
 * Created by sohobloo on 2018/1/9.
 */

public class FontsModule extends ReactContextBaseJavaModule {
    private static final String MODULE_NAME = "SHMFonts";

    private static final String SYSTEM_CONFIG_LOCATION = "/system/etc/";
    private static final String FONTS_CONFIG = "fonts.xml";
    private static final String SYSTEM_FONTS_CONFIG = "system_fonts.xml";

    FontsModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @Override
    public String getName() {
        return MODULE_NAME;
    }

    @ReactMethod
    public void fontFamilyNames(Promise promise) {
        WritableArray familyNames = null;
        File systemFontConfigLocation = new File(SYSTEM_CONFIG_LOCATION);
        File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG);
        if (!configFilename.exists()) {
            configFilename = new File(systemFontConfigLocation, SYSTEM_FONTS_CONFIG);
        }
        if (configFilename.exists()) {
            try {
                FileInputStream fontsIn = new FileInputStream(configFilename);
                List<String> familyNameList = FontListParser.parse(fontsIn);
                familyNames = Arguments.fromList(familyNameList);
            } catch (XmlPullParserException | IOException e) {
                e.printStackTrace();
            }
        }

        promise.resolve(familyNames);

    }
}

FontsPackage.java

package chuxin.shimo.shimowendang.fonts;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Created by sohobloo on 2018/1/9.
 */

public class FontsPackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new FontsModule(reactContext));
        return modules;
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

RN

RN端經過對比項目須要加載的字體和調用原生iOS/Android模塊獲取到的系統字體列表交叉對比便可知道哪些字體系統以及存在了。對比時注意一下FamilyName的大小寫/空格以及「-」鏈接符。ios

下載字體

下載不是本topic的主題,就不細講了。下載到App目錄中便可,下載前判斷一下是否已經下載云云。
因爲iOS的WKWebView沒有讀取Documents目錄權限致使真機沒法加載字體資源,根據調研須要拷貝字體文件到tmp/www/fonts目錄中。參考git

WebView動態加載字體

字體能夠經過CSS的font-face來加載,這裏就簡單了,經過insertRule傳入本地字體的familyName和path便可動態加載github

function loadFontFace (name, path) {
  const sheet = document.styleSheets[0]
  sheet.insertRule(`@font-face {font-family: '${name}'; src:url('${path}');}`, sheet.cssRules.length || 0)
}

經過調用此js函數注入webview便可實現動態加載字體,無需刷新更無需重啓APP。:-pweb

博客園的MarkDown沒有預覽功能嗎?難道大神們寫文章都這麼牛X了,排版瞭然於心?react-native

相關文章
相關標籤/搜索