diff --git a/driver/js/packages/hippy-react/src/index.ts b/driver/js/packages/hippy-react/src/index.ts index 46333be954d..1d1823189e7 100644 --- a/driver/js/packages/hippy-react/src/index.ts +++ b/driver/js/packages/hippy-react/src/index.ts @@ -62,6 +62,7 @@ const { Device, HippyRegister, ImageLoader: ImageLoaderModule, + FontLoader: FontLoaderModule, NetworkInfo: NetInfo, UIManager: UIManagerModule, flushSync, @@ -118,6 +119,7 @@ export { Clipboard, ConsoleModule, ImageLoaderModule, + FontLoaderModule, Platform, BackAndroid, Animation, diff --git a/driver/js/packages/hippy-react/src/modules/font-loader-module.ts b/driver/js/packages/hippy-react/src/modules/font-loader-module.ts new file mode 100644 index 00000000000..1afc64133fe --- /dev/null +++ b/driver/js/packages/hippy-react/src/modules/font-loader-module.ts @@ -0,0 +1,35 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Bridge } from '../global'; + +/** + * Load font from remote url. + * + * @param {string} url - Remote font url. + * @param {string} fontFamily - Remote fontFamily name. + */ +function load(fontFamily: string, url: string) { + return Bridge.callNativeWithPromise('FontLoaderModule', 'load', fontFamily, url); +} + +export { + load, +}; diff --git a/driver/js/packages/hippy-react/src/native.ts b/driver/js/packages/hippy-react/src/native.ts index b92204a81a3..bff83706612 100644 --- a/driver/js/packages/hippy-react/src/native.ts +++ b/driver/js/packages/hippy-react/src/native.ts @@ -22,6 +22,7 @@ import * as HippyGlobal from './global'; import * as Clipboard from './modules/clipboard'; import * as Cookie from './modules/cookie-module'; import * as ImageLoader from './modules/image-loader-module'; +import * as FontLoader from './modules/font-loader-module'; import * as NetworkInfo from './modules/network-info'; import * as UIManager from './modules/ui-manager-module'; import BackAndroid from './modules/back-android'; @@ -52,6 +53,7 @@ export { Device, HippyRegister, ImageLoader, + FontLoader, NetworkInfo, UIManager, flushSync, diff --git a/driver/js/packages/hippy-vue-next/src/runtime/native/index.ts b/driver/js/packages/hippy-vue-next/src/runtime/native/index.ts index 2865236fe0c..d90df8a361c 100644 --- a/driver/js/packages/hippy-vue-next/src/runtime/native/index.ts +++ b/driver/js/packages/hippy-vue-next/src/runtime/native/index.ts @@ -241,6 +241,10 @@ export interface NativeApiType { prefetch: (url: string) => void; }; + FontLoader: { + load: (fontFamily: string, url: string) => Promise; + }; + // include window and screen info Dimensions: Dimensions; @@ -517,6 +521,24 @@ export const Native: NativeApiType = { }, }, + FontLoader: { + /** + * get image size before image rendering + * + * @param fontFamily + * @param url - image url + */ + load(fontFamily, url): Promise { + return Native.callNativeWithPromise.call( + this, + 'FontLoaderModule', + 'load', + fontFamily, + url, + ); + }, + }, + /** * Get the screen or view size. */ diff --git a/driver/js/packages/hippy-vue-next/src/types/native-modules.ts b/driver/js/packages/hippy-vue-next/src/types/native-modules.ts index b57d0a98daa..f095d7fc2e0 100644 --- a/driver/js/packages/hippy-vue-next/src/types/native-modules.ts +++ b/driver/js/packages/hippy-vue-next/src/types/native-modules.ts @@ -22,6 +22,7 @@ import type { ClipboardModule } from './native-modules/clip-board-module'; import type { DeviceEventModule } from './native-modules/device-event-module'; import type { Http } from './native-modules/http'; import type { ImageLoaderModule } from './native-modules/image-loader-module'; +import type { FontLoaderModule } from './native-modules/font-loader-module'; import type { NetInfo } from './native-modules/net-info'; import type { Network } from './native-modules/network'; import type { TestModule } from './native-modules/test-module'; @@ -32,6 +33,7 @@ export interface NativeInterfaceMap { // The key here is the module name set by the native and cannot be changed at will. UIManagerModule: UiManagerModule; ImageLoaderModule: ImageLoaderModule; + FontLoaderModule: FontLoaderModule; websocket: Websocket; NetInfo: NetInfo; ClipboardModule: ClipboardModule; diff --git a/driver/js/packages/hippy-vue-next/src/types/native-modules/font-loader-module.ts b/driver/js/packages/hippy-vue-next/src/types/native-modules/font-loader-module.ts new file mode 100644 index 00000000000..16de6f1d1a3 --- /dev/null +++ b/driver/js/packages/hippy-vue-next/src/types/native-modules/font-loader-module.ts @@ -0,0 +1,23 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2022 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface FontLoaderModule { + load: (fontFamily: string, url: string) => undefined; +} diff --git a/driver/js/packages/hippy-vue/src/runtime/native.ts b/driver/js/packages/hippy-vue/src/runtime/native.ts index 5ad74bbfdd7..ab61d08a463 100644 --- a/driver/js/packages/hippy-vue/src/runtime/native.ts +++ b/driver/js/packages/hippy-vue/src/runtime/native.ts @@ -443,6 +443,20 @@ const Native: NeedToTyped = { callNative.call(this, 'ImageLoaderModule', 'prefetch', url); }, }, + /** + * operations for font + */ + FontLoader: { + /** + * Download the font from the url. + * + * @param {string} fontFamily - The font family to download, + * @param {string} url - The url where to download the font. + */ + load(fontFamily: NeedToTyped, url: NeedToTyped) { + return callNativeWithPromise.call(this, 'FontLoaderModule', 'load', fontFamily, url); + }, + }, /** * Network operations */ diff --git a/framework/android/src/main/java/com/tencent/mtt/hippy/bridge/HippyCoreAPI.java b/framework/android/src/main/java/com/tencent/mtt/hippy/bridge/HippyCoreAPI.java index e399f4e94f1..165fefc89ed 100644 --- a/framework/android/src/main/java/com/tencent/mtt/hippy/bridge/HippyCoreAPI.java +++ b/framework/android/src/main/java/com/tencent/mtt/hippy/bridge/HippyCoreAPI.java @@ -27,6 +27,7 @@ import com.tencent.mtt.hippy.modules.nativemodules.console.ConsoleModule; import com.tencent.mtt.hippy.modules.nativemodules.deviceevent.DeviceEventModule; import com.tencent.mtt.hippy.modules.nativemodules.exception.ExceptionModule; +import com.tencent.mtt.hippy.modules.nativemodules.font.FontLoaderModule; import com.tencent.mtt.hippy.modules.nativemodules.image.ImageLoaderModule; import com.tencent.mtt.hippy.modules.nativemodules.netinfo.NetInfoModule; import com.tencent.mtt.hippy.modules.nativemodules.network.NetworkModule; @@ -84,6 +85,12 @@ public HippyNativeModuleBase get() { return new ImageLoaderModule(context); } }); + modules.put(FontLoaderModule.class, new Provider() { + @Override + public HippyNativeModuleBase get() { + return new FontLoaderModule(context); + } + }); modules.put(NetworkModule.class, new Provider() { @Override public HippyNativeModuleBase get() { diff --git a/framework/android/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/font/FontLoaderModule.java b/framework/android/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/font/FontLoaderModule.java new file mode 100644 index 00000000000..6dcde9bacf9 --- /dev/null +++ b/framework/android/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/font/FontLoaderModule.java @@ -0,0 +1,46 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.modules.nativemodules.font; + +import com.tencent.mtt.hippy.HippyEngineContext; +import com.tencent.mtt.hippy.annotation.HippyMethod; +import com.tencent.mtt.hippy.annotation.HippyNativeModule; +import com.tencent.mtt.hippy.modules.Promise; +import com.tencent.mtt.hippy.modules.nativemodules.HippyNativeModuleBase; +import com.tencent.renderer.NativeRender; +import com.tencent.renderer.NativeRendererManager; +import com.tencent.renderer.component.text.FontLoader; + +@HippyNativeModule(name = "FontLoaderModule") +public class FontLoaderModule extends HippyNativeModuleBase { + + private final FontLoader mFontLoader; + private final NativeRender mNativeRender; + private final int rootId; + + public FontLoaderModule(HippyEngineContext context) { + super(context); + mFontLoader = new FontLoader(context.getVfsManager()); + mNativeRender = NativeRendererManager.getNativeRenderer(context.getRootView().getContext()); + rootId = context.getRootView().getId(); + } + + @HippyMethod(name = "load") + public void load(final String fontFamily, final String fontUrl, final Promise promise) { + mFontLoader.loadAndFresh(fontFamily, fontUrl, mNativeRender, rootId, promise); + } +} diff --git a/framework/ios/module/fontLoader/HippyFontLoaderModule.h b/framework/ios/module/fontLoader/HippyFontLoaderModule.h new file mode 100644 index 00000000000..a87e59f8997 --- /dev/null +++ b/framework/ios/module/fontLoader/HippyFontLoaderModule.h @@ -0,0 +1,36 @@ +/*! +* iOS SDK +* +* Tencent is pleased to support the open source community by making +* Hippy available. +* +* Copyright (C) 2019 THL A29 Limited, a Tencent company. +* All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#import +#import "HippyBridgeModule.h" + +static NSString *const HippyLoadFontNotification = @"HippyLoadFontNotification"; + +@interface HippyFontLoaderModule : NSObject + +@property (nonatomic, readonly) NSString *fontDir; +@property (nonatomic, readonly) NSString *fontUrlCachePath; + +- (NSString *)getFontPath:(NSString *)url; +- (BOOL)registerFontFromURL:(NSString *)urlString error:(NSError *)error; + +@end diff --git a/framework/ios/module/fontLoader/HippyFontLoaderModule.mm b/framework/ios/module/fontLoader/HippyFontLoaderModule.mm new file mode 100644 index 00000000000..90cc74c8082 --- /dev/null +++ b/framework/ios/module/fontLoader/HippyFontLoaderModule.mm @@ -0,0 +1,191 @@ +/*! +* iOS SDK +* +* Tencent is pleased to support the open source community by making +* Hippy available. +* +* Copyright (C) 2019 THL A29 Limited, a Tencent company. +* All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#import +#import "HippyFontLoaderModule.h" +#import +#import "HippyBridge+Private.h" +#import "HippyBridge+VFSLoader.h" +#import "HippyLog.h" +#import "VFSUriLoader.h" +#import "HippyUIManager.h" + + +static NSString *const kFontLoaderModuleErrorDomain = @"kFontLoaderModuleErrorDomain"; +static NSUInteger const FontLoaderErrorUrlError = 1; +static NSUInteger const FontLoaderErrorDirectoryError = 2; +static NSUInteger const FontLoaderErrorRequestError = 3; +static NSUInteger const FontLoaderErrorRegisterError = 4; + +@interface HippyFontLoaderModule () { +} + +@end + +@implementation HippyFontLoaderModule + +HIPPY_EXPORT_MODULE(FontLoaderModule) + +@synthesize bridge = _bridge; + +- (instancetype)init { + if ((self = [super init])) { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + NSString *cachesDirectory = [paths objectAtIndex:0]; + _fontDir = [cachesDirectory stringByAppendingPathComponent:@"font"]; + _fontUrlCachePath = [_fontDir stringByAppendingPathComponent:@"fontUrlCache.plist"]; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter addObserver:self selector:@selector(loadAndRegisterFont:) name:HippyLoadFontNotification object:nil]; + }); + } + return self; +} + +- (void)loadAndRegisterFont:(NSNotification *)notification { + NSLog(@"handle notification"); + NSString *urlString = [notification.userInfo objectForKey:@"fontUrl"]; + NSString *fontFamily = [notification.userInfo objectForKey:@"fontFamily"]; + [self load:fontFamily from:urlString resolver:^(id result) {} rejecter:^(NSString *code, NSString *message, NSError *error) {}]; +} + +- (NSString *)getFontPath:(NSString *)url{ + NSMutableDictionary *fontUrlDict = [NSMutableDictionary dictionaryWithContentsOfFile:self.fontUrlCachePath]; + if (fontUrlDict == nil) { + fontUrlDict = [NSMutableDictionary dictionary]; + } + NSString *fontFile = fontUrlDict[url]; + if (!fontFile) { + return nil; + } + NSString *fontPath = [self.fontDir stringByAppendingPathComponent:fontFile]; + NSFileManager *fileManager = [NSFileManager defaultManager]; + if (![fileManager fileExistsAtPath:fontPath]) { + return nil; + } + return fontPath; +} + +- (BOOL)isFontRegistered:(NSString *)fontName { + NSArray *fontFamilyNames = [UIFont familyNames]; + for (NSString *familyName in fontFamilyNames) { + NSArray *fontNames = [UIFont fontNamesForFamilyName:familyName]; + if ([fontNames containsObject:fontName]) { + return YES; + } + } + return NO; +} + +- (BOOL)registerFontFromURL:(NSString *)urlString error:(NSError *)error { + NSURL *url = [NSURL fileURLWithPath:urlString]; + CGDataProviderRef fontDataProvider = CGDataProviderCreateWithURL((CFURLRef)url); + CGFontRef font = CGFontCreateWithDataProvider(fontDataProvider); + CGDataProviderRelease(fontDataProvider); + if (!font) { + error = [NSError errorWithDomain:kFontLoaderModuleErrorDomain + code:FontLoaderErrorRegisterError userInfo:@{@"reason": @"font dosen't exist"}]; + return NO; + } + CFStringRef fontNameRef = CGFontCopyPostScriptName(font); + NSString *fontName = (__bridge_transfer NSString *)fontNameRef; + if ([self isFontRegistered:fontName]) { + NSLog(@"already registered"); + return YES; + } + CFErrorRef cfError; + BOOL success = CTFontManagerRegisterGraphicsFont(font, &cfError); + CFRelease(font); + if (!success) { + error = CFBridgingRelease(cfError); + return NO; + } + NSLog(@"registering font success"); + return YES; +} + +- (BOOL)cacheFont:(NSString *)fontFileName url:(NSString *)url { + NSMutableDictionary *fontUrlDict = [NSMutableDictionary dictionaryWithContentsOfFile:self.fontUrlCachePath]; + if (fontUrlDict == nil) { + fontUrlDict = [NSMutableDictionary dictionary]; + } + [fontUrlDict setObject:fontFileName forKey:url]; + return [fontUrlDict writeToFile:self.fontUrlCachePath atomically:YES]; +} + + +HIPPY_EXPORT_METHOD(load:(NSString *)fontFamily from:(NSString *)urlString resolver:(HippyPromiseResolveBlock)resolve rejecter:(HippyPromiseRejectBlock)reject) { + if (!urlString) { + NSError *error = [NSError errorWithDomain:kFontLoaderModuleErrorDomain + code:FontLoaderErrorUrlError userInfo:@{@"reason": @"url is empty"}]; + NSString *errorKey = [NSString stringWithFormat:@"%lu", FontLoaderErrorUrlError]; + reject(errorKey, @"url is empty", error); + return; + } + NSFileManager *fileManager = [NSFileManager defaultManager]; + + // Create font directory if not exist + if (![fileManager fileExistsAtPath:self.fontDir]) { + NSError *error; + [fileManager createDirectoryAtPath:self.fontDir withIntermediateDirectories:YES attributes:nil error:&error]; + if (error) { + NSString *errorKey = [NSString stringWithFormat:@"%lu", FontLoaderErrorDirectoryError]; + reject(errorKey, @"directory create error", error); + return; + } + } + NSLog(@"urlString: %@", urlString); + + [self.bridge loadContentsAsynchronouslyFromUrl:urlString + method:@"Get" + params:nil + body:nil + queue:nil + progress:nil + completionHandler:^(NSData *data, NSDictionary *userInfo, NSURLResponse *response, NSError *error) { + NSLog(@"complete:"); + if (error) { + NSString *errorKey = [NSString stringWithFormat:@"%lu", FontLoaderErrorRequestError]; + NSLog(@"font request error:%@", error.description); + reject(errorKey, @"font request error", error); + return; + } + NSString *fileName = [fontFamily stringByAppendingFormat:@".%@", [response.suggestedFilename pathExtension]]; + NSString *fontFilePath = [self.fontDir stringByAppendingPathComponent:fileName]; + NSLog(@"fontFilePath: %@", fontFilePath); + [data writeToFile:fontFilePath atomically:YES]; + [self cacheFont:fileName url:urlString]; + + if ([self registerFontFromURL:fontFilePath error:error]) { + [[NSNotificationCenter defaultCenter] postNotificationName:HippyFontChangeTriggerNotification object:nil]; + resolve(nil); + } else { + NSString *errorKey = [NSString stringWithFormat:@"%lu", FontLoaderErrorRegisterError]; + reject(errorKey, @"register false", error); + } + }]; + NSLog(@"out:"); +} + +@end diff --git a/renderer/native/android/src/main/cpp/include/renderer/native_render_jni.h b/renderer/native/android/src/main/cpp/include/renderer/native_render_jni.h index 2abeb3bf21e..9af8185a72d 100644 --- a/renderer/native/android/src/main/cpp/include/renderer/native_render_jni.h +++ b/renderer/native/android/src/main/cpp/include/renderer/native_render_jni.h @@ -41,6 +41,10 @@ jobject GetNativeRendererInstance(JNIEnv* j_env, jobject j_object, jint j_render_manager_id); +void MarkTextNodeDirty(JNIEnv *j_env, jobject j_object, jint j_root_id); + +void FreshWindow(JNIEnv *j_env, jobject j_object, jint j_render_manager_id, jint j_root_id); + void UpdateRootSize(JNIEnv* j_env, jobject j_obj, jint j_render_manager_id, jint j_root_id, jfloat width, jfloat height); diff --git a/renderer/native/android/src/main/cpp/src/renderer/native_render_jni.cc b/renderer/native/android/src/main/cpp/src/renderer/native_render_jni.cc index 59c8113db93..f2f10fd4052 100644 --- a/renderer/native/android/src/main/cpp/src/renderer/native_render_jni.cc +++ b/renderer/native/android/src/main/cpp/src/renderer/native_render_jni.cc @@ -77,6 +77,16 @@ REGISTER_JNI("com/tencent/renderer/NativeRenderProvider", "(IIFF)V", UpdateRootSize) +REGISTER_JNI("com/tencent/renderer/NativeRenderProvider", + "freshWindow", + "(II)V", + FreshWindow) + +REGISTER_JNI("com/tencent/renderer/NativeRenderProvider", + "markTextNodeDirty", + "(I)V", + MarkTextNodeDirty) + static jint JNI_OnLoad(__unused JavaVM* j_vm, __unused void* reserved) { auto j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); @@ -149,6 +159,62 @@ jobject GetNativeRendererInstance(JNIEnv* j_env, jobject j_object, jint j_render return nullptr; } +void MarkTextNodeDirtyRecursive(const std::shared_ptr& node) { + if (!node) { + return; + } + uint32_t child_count = node->GetChildCount(); + for (uint32_t i = 0; i < child_count; i++) { + MarkTextNodeDirtyRecursive(node->GetChildAt(i)); + } + if (node->GetViewName() == "TextInput" || node->GetViewName() == "Text") { + auto layout_node = node->GetLayoutNode(); + layout_node->MarkDirty(); + } +} + +void MarkTextNodeDirty(JNIEnv *j_env, jobject j_object, jint j_root_id) { + auto& root_map = RootNode::PersistentMap(); + std::shared_ptr root_node; + uint32_t root_id = footstone::check::checked_numeric_cast(j_root_id); + bool ret = root_map.Find(root_id, root_node); + if (!ret) { + FOOTSTONE_DLOG(WARNING) << "root_node is nullptr"; + return; + } + MarkTextNodeDirtyRecursive(root_node); +} + +void FreshWindow(JNIEnv *j_env, jobject j_object, jint j_render_manager_id, jint j_root_id) { + auto& map = NativeRenderManager::PersistentMap(); + std::shared_ptr render_manager; + bool ret = map.Find(static_cast(j_render_manager_id), render_manager); + if (!ret) { + FOOTSTONE_DLOG(WARNING) << "FreshWindow j_render_manager_id invalid"; + return; + } + std::shared_ptr dom_manager = render_manager->GetDomManager(); + if (dom_manager == nullptr) { + FOOTSTONE_DLOG(WARNING) << "FreshWindow dom_manager is nullptr"; + return; + } + auto& root_map = RootNode::PersistentMap(); + std::shared_ptr root_node; + uint32_t root_id = footstone::check::checked_numeric_cast(j_root_id); + ret = root_map.Find(root_id, root_node); + if (!ret) { + FOOTSTONE_DLOG(WARNING) << "FreshWindow root_node is nullptr"; + return; + } + + std::vector> ops; + ops.emplace_back([dom_manager, root_node]{ + dom_manager->DoLayout(root_node); + dom_manager->EndBatch(root_node); + }); + dom_manager->PostTask(Scene(std::move(ops))); +} + void UpdateRootSize(JNIEnv *j_env, jobject j_object, jint j_render_manager_id, jint j_root_id, jfloat j_width, jfloat j_height) { auto& map = NativeRenderManager::PersistentMap(); diff --git a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/dom/node/NodeProps.java b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/dom/node/NodeProps.java index c1c9b1346d6..4d3048e8e0c 100644 --- a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/dom/node/NodeProps.java +++ b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/dom/node/NodeProps.java @@ -94,6 +94,7 @@ public class NodeProps { public static final String FONT_WEIGHT = "fontWeight"; public static final String FONT_STYLE = "fontStyle"; public static final String FONT_FAMILY = "fontFamily"; + public static final String FONT_URL = "fontUrl"; public static final String LINE_HEIGHT = "lineHeight"; public static final String LINE_SPACING_MULTIPLIER = "lineSpacingMultiplier"; public static final String LINE_SPACING_EXTRA = "lineSpacingExtra"; diff --git a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInput.java b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInput.java index 231aefda270..660dea5cb08 100644 --- a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInput.java +++ b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInput.java @@ -55,6 +55,7 @@ import com.tencent.renderer.NativeRendererManager; import com.tencent.renderer.component.Component; import com.tencent.renderer.component.text.FontAdapter; +import com.tencent.renderer.component.text.FontLoader; import com.tencent.renderer.component.text.TypeFaceUtil; import com.tencent.renderer.node.RenderNode; import com.tencent.renderer.utils.EventUtils; @@ -96,6 +97,7 @@ public class HippyTextInput extends AppCompatEditText implements HippyViewBase, private int mLineHeight = 0; @Nullable private String mFontFamily; + private String mFontUrl; private Paint mTextPaint; public HippyTextInput(Context context) { @@ -208,7 +210,6 @@ public void setTextLineHeight(int lineHeight) { public void onBatchComplete() { if (mShouldUpdateTypeface) { updateTypeface(); - mShouldUpdateTypeface = false; } if (!mShouldUpdateLineHeight) { return; @@ -762,6 +763,13 @@ public void setFontFamily(String family) { } } + public void setFontUrl(String fontUrl) { + if (!Objects.equals(mFontUrl, fontUrl)) { + mFontUrl = fontUrl; + mShouldUpdateTypeface = true; + } + } + public void setFontWeight(String weight) { if (!mFontWeight.equals(weight)) { mFontWeight = weight; @@ -776,6 +784,16 @@ private void updateTypeface() { mTextPaint.reset(); } NativeRender nativeRenderer = NativeRendererManager.getNativeRenderer(getContext()); + if (mFontUrl != null) { + FontLoader loader = nativeRenderer == null ? null : nativeRenderer.getFontLoader(); + if (loader != null) { + int rootId = nativeRenderer.getRootView(this).getId(); + mShouldUpdateTypeface = loader.loadIfNeeded(mFontFamily, mFontUrl, nativeRenderer, rootId); + } + } + else { + mShouldUpdateTypeface = false; + } FontAdapter fontAdapter = nativeRenderer == null ? null : nativeRenderer.getFontAdapter(); TypeFaceUtil.apply(mTextPaint, mItalic, mFontWeight, mFontFamily, fontAdapter); setTypeface(mTextPaint.getTypeface(), mTextPaint.isFakeBoldText() ? Typeface.BOLD : Typeface.NORMAL); diff --git a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInputController.java b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInputController.java index c5e87494e82..e15244195d2 100644 --- a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInputController.java +++ b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInputController.java @@ -241,6 +241,11 @@ public void setFontFamily(HippyTextInput view, String fontFamily) { view.setFontFamily(fontFamily); } + @HippyControllerProps(name = NodeProps.FONT_URL, defaultType = HippyControllerProps.STRING) + public void setFontUrl(HippyTextInput view, String fontUrl) { + view.setFontUrl(fontUrl); + } + private static final InputFilter[] EMPTY_FILTERS = new InputFilter[0]; @HippyControllerProps(name = "maxLength", defaultType = HippyControllerProps.NUMBER, defaultNumber = Integer.MAX_VALUE) diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/NativeRender.java b/renderer/native/android/src/main/java/com/tencent/renderer/NativeRender.java index 64dfbffe267..5826310481e 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/NativeRender.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/NativeRender.java @@ -29,6 +29,7 @@ import com.tencent.renderer.component.image.ImageDecoderAdapter; import com.tencent.renderer.component.image.ImageLoaderAdapter; import com.tencent.renderer.component.text.FontAdapter; +import com.tencent.renderer.component.text.FontLoader; import com.tencent.renderer.node.VirtualNode; import com.tencent.renderer.utils.EventUtils.EventType; @@ -52,6 +53,9 @@ public interface NativeRender extends RenderExceptionHandler, RenderLogHandler { @Nullable ImageLoaderAdapter getImageLoader(); + @Nullable + FontLoader getFontLoader(); + @Nullable VfsManager getVfsManager(); @@ -105,6 +109,10 @@ VirtualNode createVirtualNode(int rootId, int id, int pid, int index, @NonNull S void onSizeChanged(int rootId, int nodeId, int width, int height, boolean isSync); + void markTextNodeDirty(int rootId); + + void freshWindow(int rootId); + void updateDimension(int width, int height); void dispatchEvent(int rootId, int nodeId, @NonNull String eventName, diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/NativeRenderProvider.java b/renderer/native/android/src/main/java/com/tencent/renderer/NativeRenderProvider.java index e2a6a55e64e..f31df01578b 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/NativeRenderProvider.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/NativeRenderProvider.java @@ -337,6 +337,10 @@ public void onSizeChanged(int rootId, int width, int height) { updateRootSize(mInstanceId, rootId, PixelUtil.px2dp(width), PixelUtil.px2dp(height)); } + public void freshWindow(int rootId) { + freshWindow(mInstanceId, rootId); + } + public void onSizeChanged(int rootId, int nodeId, int width, int height, boolean isSync) { updateNodeSize(mInstanceId, rootId, nodeId, PixelUtil.px2dp(width), PixelUtil.px2dp(height), isSync); @@ -433,6 +437,25 @@ private void dispatchEventImpl(int rootId, int nodeId, @NonNull String eventName @SuppressWarnings("JavaJniMissingFunction") private native void updateRootSize(int instanceId, int rootId, float width, float height); + /** + * Call back from Android system when size changed, just like horizontal and vertical screen + * switching, call this jni interface to invoke dom tree relayout. + * + * @param rootId the root node id + * @param instanceId the unique id of native (C++) render manager + */ + @SuppressWarnings("JavaJniMissingFunction") + private native void freshWindow(int instanceId, int rootId); + + /** + * Call back from Android system when size changed, just like horizontal and vertical screen + * switching, call this jni interface to invoke dom tree relayout. + * + * @param rootId the root node id + */ + @SuppressWarnings("JavaJniMissingFunction") + public native void markTextNodeDirty(int rootId); + /** * Updates the size to the specified node, such as modal node, should set new window size before * layout. diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/NativeRenderer.java b/renderer/native/android/src/main/java/com/tencent/renderer/NativeRenderer.java index 8e97cc2b838..875f0e917ee 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/NativeRenderer.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/NativeRenderer.java @@ -50,6 +50,7 @@ import com.tencent.renderer.component.image.ImageLoader; import com.tencent.renderer.component.image.ImageLoaderAdapter; import com.tencent.renderer.component.text.FontAdapter; +import com.tencent.renderer.component.text.FontLoader; import com.tencent.renderer.component.text.TextRenderSupplier; import com.tencent.renderer.node.ListItemRenderNode; import com.tencent.renderer.node.RenderNode; @@ -136,6 +137,8 @@ public class NativeRenderer extends Renderer implements NativeRender, NativeRend private ExecutorService mBackgroundExecutor; @Nullable private ImageLoaderAdapter mImageLoader; + @Nullable + private FontLoader mFontLoader; public enum FCPBatchState { WATCHING, @@ -223,6 +226,14 @@ public ImageLoaderAdapter getImageLoader() { return mImageLoader; } + @Nullable + public FontLoader getFontLoader() { + if (mFontLoader == null && getVfsManager() != null) { + mFontLoader = new FontLoader(getVfsManager()); + } + return mFontLoader; + } + @Override @Nullable public VfsManager getVfsManager() { @@ -406,6 +417,14 @@ private void onSizeChanged(int rootId, int w, int h) { mRenderProvider.onSizeChanged(rootId, w, h); } + public void markTextNodeDirty(int rootId) { + mRenderProvider.markTextNodeDirty(rootId); + } + + public void freshWindow(int rootId) { + mRenderProvider.freshWindow(rootId); + } + @Override public void onSizeChanged(int rootId, int w, int h, int ow, int oh) { FrameworkProxy frameworkProxy = getFrameworkProxy(); diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/component/text/FontLoader.java b/renderer/native/android/src/main/java/com/tencent/renderer/component/text/FontLoader.java new file mode 100644 index 00000000000..58a8d9f442b --- /dev/null +++ b/renderer/native/android/src/main/java/com/tencent/renderer/component/text/FontLoader.java @@ -0,0 +1,157 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.renderer.component.text; + +import android.text.TextUtils; + +import androidx.annotation.NonNull; + +import com.tencent.mtt.hippy.modules.Promise; +import com.tencent.mtt.hippy.utils.ContextHolder; +import com.tencent.mtt.hippy.utils.LogUtils; +import com.tencent.renderer.NativeRender; +import com.tencent.vfs.ResourceDataHolder; +import com.tencent.vfs.VfsManager; +import com.tencent.vfs.VfsManager.FetchResourceCallback; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Arrays; +import java.util.HashMap; + +public class FontLoader { + + private final VfsManager mVfsManager; + private final File mFontDir; + private final File mFontUrlDictPath; + private HashMap mFontUrlDict; + private static final String[] allowedExtensions = {"otf", "ttf"}; + + public FontLoader(VfsManager vfsManager) { + mVfsManager = vfsManager; + mFontDir = new File(ContextHolder.getAppContext().getCacheDir(), "fonts");; + mFontUrlDictPath = new File(mFontDir, "fontUrlDict.ser"); + try (FileInputStream fis = new FileInputStream(mFontUrlDictPath); + ObjectInputStream ois = new ObjectInputStream(fis)) { + mFontUrlDict = (HashMap) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + mFontUrlDict = new HashMap<>(); + } + } + + private void saveFontFile(byte[] byteArray, String fileName, Promise promise) { + if (!mFontDir.exists()) { + if (!mFontDir.mkdirs()) { + if (promise != null) { + promise.reject("Create font directory failed"); + } + return; + } + } + File fontFile = new File(mFontDir, fileName); + try (FileOutputStream fos = new FileOutputStream(fontFile)) { + fos.write(byteArray); + if (promise != null) { + promise.resolve(null); + } + } catch (IOException e) { + if (promise != null) { + promise.reject("Write font file failed:" + e.getMessage()); + } + } + } + + private void saveFontUrlDictFile() { + try (FileOutputStream fos = new FileOutputStream(mFontUrlDictPath); + ObjectOutputStream oos = new ObjectOutputStream(fos)) { + oos.writeObject(mFontUrlDict); + LogUtils.d("FontLoader", "save fontUrlDict.ser success"); + } catch (IOException e) { + LogUtils.d("FontLoader", "save fontUrlDict.ser failed"); + } + } + + public static String getFileExtension(String url) { + int dotIndex = url.lastIndexOf('.'); + if (dotIndex > 0 && dotIndex < url.length() - 1) { + String ext = url.substring(dotIndex + 1).toLowerCase(); + if (Arrays.asList(allowedExtensions).contains(ext)) { + return "." + ext; + } + } + return ""; + } + + public boolean loadIfNeeded(final String fontFamily, final String fontUrl, NativeRender render, + int rootId) { + String fontFileName = mFontUrlDict.get(fontUrl); + if (fontFileName != null) { + File fontFile = new File(mFontDir, fontFileName); + if (fontFile.exists()) { + return false; + } + } + loadAndFresh(fontFamily, fontUrl, render, rootId, null); + return true; + } + + + public void loadAndFresh(final String fontFamily, final String fontUrl, NativeRender render, + int rootId, Promise promise) { + LogUtils.d("FontLoader", "start load" + fontUrl); + if (TextUtils.isEmpty(fontUrl)) { + if (promise != null) { + promise.reject("Url parameter is empty!"); + } + return; + } + mVfsManager.fetchResourceAsync(fontUrl, null, null, + new FetchResourceCallback() { + @Override + public void onFetchCompleted(@NonNull final ResourceDataHolder dataHolder) { + byte[] bytes = dataHolder.getBytes(); + if (dataHolder.resultCode + != ResourceDataHolder.RESOURCE_LOAD_SUCCESS_CODE || bytes == null + || bytes.length <= 0) { + String message = + dataHolder.errorMessage != null ? dataHolder.errorMessage : ""; + if (promise != null) { + promise.reject("Fetch font failed, url=" + fontUrl + ", msg=" + message); + } + } else { + String fileName = fontFamily + getFileExtension(fontUrl); + saveFontFile(bytes, fileName, promise); + mFontUrlDict.put(fontUrl, fileName); + saveFontUrlDictFile(); + TypeFaceUtil.clearFontCache(fontFamily); + render.markTextNodeDirty(rootId); + render.freshWindow(rootId); + } + dataHolder.recycle(); + } + + @Override + public void onFetchProgress(long total, long loaded) { + // Nothing need to do here. + } + }); + } +} diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TypeFaceUtil.java b/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TypeFaceUtil.java index 3dd6043f22b..6e49a88ee4b 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TypeFaceUtil.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TypeFaceUtil.java @@ -22,12 +22,14 @@ import android.os.Build.VERSION_CODES; import android.text.TextUtils; +import android.util.Log; import android.util.SparseArray; import androidx.annotation.Nullable; import com.tencent.mtt.hippy.utils.ContextHolder; import com.tencent.mtt.hippy.utils.LogUtils; +import java.io.File; import java.util.HashMap; import java.util.Map; @@ -40,7 +42,7 @@ public class TypeFaceUtil { public static final String TEXT_FONT_STYLE_NORMAL = "normal"; private static final String TAG = "TypeFaceUtil"; private static final String[] EXTENSIONS = {"", "_bold", "_italic", "_bold_italic"}; - private static final String[] FONT_EXTENSIONS = {".ttf", ".otf"}; + private static final String[] FONT_EXTENSIONS = {".ttf", ".otf", ""}; private static final String FONTS_PATH = "fonts/"; private static final Map> sFontCache = new HashMap<>(); @@ -79,6 +81,30 @@ public static Typeface getTypeface(String fontFamilyName, String weight, boolean return typeface; } + public static void clearFontCache(String fontFamilyName) { + sFontCache.remove(fontFamilyName); + } + + private static Typeface createExactTypeFace(String fileName) { + // create from assets + Typeface typeface = null; + try { + typeface = Typeface.createFromAsset(ContextHolder.getAppContext().getAssets(), fileName); + } catch (Exception e) { + LogUtils.w(TAG, e.getMessage()); + } + // create from cache dir + if (typeface == null || typeface.equals(Typeface.DEFAULT)) { + try { + File cacheDir = ContextHolder.getAppContext().getCacheDir(); + typeface = Typeface.createFromFile(new File(cacheDir, fileName)); + } catch (Exception e) { + LogUtils.w(TAG, e.getMessage()); + } + } + return typeface; + } + private static Typeface createTypeface(String fontFamilyName, int weightNumber, int style, boolean italic, @Nullable FontAdapter fontAdapter) { final String extension = EXTENSIONS[style]; @@ -94,22 +120,16 @@ private static Typeface createTypeface(String fontFamilyName, int weightNumber, } for (String fileExtension : FONT_EXTENSIONS) { String fileName = FONTS_PATH + splitName + extension + fileExtension; - try { - Typeface typeface = Typeface.createFromAsset(ContextHolder.getAppContext().getAssets(), fileName); - if (typeface != null && !typeface.equals(Typeface.DEFAULT)) { - return typeface; - } - } catch (Exception e) { - // If create type face from asset failed, other builder can also be used - LogUtils.w(TAG, e.getMessage()); - } - if (style == Typeface.NORMAL) { - continue; + Typeface typeface = createExactTypeFace(fileName); + if (typeface != null && !typeface.equals(Typeface.DEFAULT)) { + return typeface; } - // try to load font file without extension - fileName = FONTS_PATH + splitName + fileExtension; - try { - Typeface typeface = Typeface.createFromAsset(ContextHolder.getAppContext().getAssets(), fileName); + } + // try to load font file without extension + if (style != Typeface.NORMAL) { + for (String fileExtension : FONT_EXTENSIONS) { + String fileName = FONTS_PATH + splitName + fileExtension; + Typeface typeface = createExactTypeFace(fileName); if (typeface != null && !typeface.equals(Typeface.DEFAULT)) { if (VERSION.SDK_INT >= VERSION_CODES.P && weightNumber > 0) { return Typeface.create(typeface, weightNumber, italic); @@ -117,8 +137,6 @@ private static Typeface createTypeface(String fontFamilyName, int weightNumber, // "bold" has no effect on api level < P, prefer to use `Paint.setFakeBoldText(boolean)` return italic ? Typeface.create(typeface, Typeface.ITALIC) : typeface; } - } catch (Exception e) { - LogUtils.w(TAG, e.getMessage()); } } if (fontAdapter != null) { diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/node/TextVirtualNode.java b/renderer/native/android/src/main/java/com/tencent/renderer/node/TextVirtualNode.java index fb4d89e38d6..49e9304d2ad 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/node/TextVirtualNode.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/node/TextVirtualNode.java @@ -44,6 +44,7 @@ import com.tencent.mtt.hippy.utils.PixelUtil; import com.tencent.renderer.NativeRender; import com.tencent.renderer.component.text.FontAdapter; +import com.tencent.renderer.component.text.FontLoader; import com.tencent.renderer.component.text.TextDecorationSpan; import com.tencent.renderer.component.text.TextForegroundColorSpan; import com.tencent.renderer.component.text.TextGestureSpan; @@ -110,6 +111,10 @@ public class TextVirtualNode extends VirtualNode { @Nullable protected String mFontFamily; @Nullable + protected String mFontUrl; + @Nullable + protected FontLoader mFontLoader; + @Nullable protected SpannableStringBuilder mSpanned; @Nullable protected CharSequence mText; @@ -124,11 +129,14 @@ public class TextVirtualNode extends VirtualNode { @Nullable protected Layout mLayout; protected int mBackgroundColor = Color.TRANSPARENT; + protected NativeRender mNativeRender; public TextVirtualNode(int rootId, int id, int pid, int index, @NonNull NativeRender nativeRender) { super(rootId, id, pid, index); + mNativeRender = nativeRender; mFontAdapter = nativeRender.getFontAdapter(); + mFontLoader = nativeRender.getFontLoader(); if (I18nUtil.isRTL()) { mAlignment = Layout.Alignment.ALIGN_OPPOSITE; } @@ -181,6 +189,14 @@ public void setFontFamily(String family) { } } + @HippyControllerProps(name = NodeProps.FONT_URL, defaultType = HippyControllerProps.STRING) + public void setFontUrl(String fontUrl) { + if (!Objects.equals(mFontUrl, fontUrl)) { + mFontUrl = fontUrl; + markDirty(); + } + } + @SuppressWarnings("unused") @HippyControllerProps(name = NodeProps.FONT_WEIGHT, defaultType = HippyControllerProps.STRING) public void setFontWeight(String weight) { @@ -469,6 +485,9 @@ protected void createSpanOperationImpl(@NonNull List ops, if (mFontAdapter != null && mEnableScale) { size = (int) (size * mFontAdapter.getFontScale()); } + if (mFontUrl != null && mFontLoader != null) { + mFontLoader.loadIfNeeded(mFontFamily, mFontUrl, mNativeRender, getRootId()); + } ops.add(new SpanOperation(start, end, new AbsoluteSizeSpan(size))); ops.add(new SpanOperation(start, end, new TextStyleSpan(mItalic, mFontWeight, mFontFamily, mFontAdapter))); if (mShadowOffsetDx != 0 || mShadowOffsetDy != 0) { diff --git a/renderer/native/ios/renderer/HippyFont.h b/renderer/native/ios/renderer/HippyFont.h index f120b8f779b..9e0dcfa3784 100644 --- a/renderer/native/ios/renderer/HippyFont.h +++ b/renderer/native/ios/renderer/HippyFont.h @@ -33,6 +33,7 @@ */ + (UIFont *)updateFont:(UIFont *)font withFamily:(NSString *)family + url:(NSString *)url size:(NSNumber *)size weight:(NSString *)weight style:(NSString *)style diff --git a/renderer/native/ios/renderer/HippyFont.mm b/renderer/native/ios/renderer/HippyFont.mm index ef945ca6f13..af0c362a9e4 100644 --- a/renderer/native/ios/renderer/HippyFont.mm +++ b/renderer/native/ios/renderer/HippyFont.mm @@ -24,6 +24,7 @@ #import "HippyFont.h" #import "HippyLog.h" +#import "HippyFontLoaderModule.h" static NSCache *fontCache; @@ -102,7 +103,7 @@ struct __attribute__((__packed__)) CacheKey { [cache removeAllObjects]; }]; }); - + NSArray *names = [cache objectForKey:familyName]; if (!names) { names = [UIFont fontNamesForFamilyName:familyName] ?: [NSArray new]; @@ -115,8 +116,9 @@ @implementation HippyConvert (NativeRenderFont) + (UIFont *)UIFont:(id)json { json = [self NSDictionary:json]; - return [HippyFont updateFont:nil + return [HippyFont updateFont:nil withFamily:[HippyConvert NSString:json[@"fontFamily"]] + url:[HippyConvert NSString:json[@"fontUrl"]] size:[HippyConvert NSNumber:json[@"fontSize"]] weight:[HippyConvert NSString:json[@"fontWeight"]] style:[HippyConvert NSString:json[@"fontStyle"]] @@ -197,11 +199,29 @@ + (void)initialize { + (UIFont *)updateFont:(UIFont *)font withFamily:(NSString *)family + url:(NSString *)url size:(NSNumber *)size weight:(NSString *)weight style:(NSString *)style variant:(NSArray *)variant scaleMultiplier:(CGFloat)scaleMultiplier { + // Defaults + if (url) { + HippyFontLoaderModule *fontLoader = [[HippyFontLoaderModule alloc] init]; + NSString *fontPath = [fontLoader getFontPath:url]; + if (fontPath) { + NSError *error = nil; + [fontLoader registerFontFromURL:fontPath error:error]; + if (error) { + HippyLogError(@"register font failed: %@", error.description); + } + } + else { + NSDictionary *userInfo = @{@"fontUrl": url, @"fontFamily": family}; + [[NSNotificationCenter defaultCenter] postNotificationName:HippyLoadFontNotification object:nil userInfo:userInfo]; + } + } + // Defaults static NSString *defaultFontFamily; static dispatch_once_t onceToken; @@ -311,7 +331,7 @@ + (UIFont *)updateFont:(UIFont *)font if (!font && names.count > 0) { font = [UIFont fontWithName:names[0] size:fontSize]; } - + // Apply font variants to font object if (variant) { NSArray *fontFeatures = [HippyConvert NativeRenderFontVariantDescriptorArray:variant]; diff --git a/renderer/native/ios/renderer/component/text/HippyShadowText.h b/renderer/native/ios/renderer/component/text/HippyShadowText.h index a9be9a1525b..94e6cb4bbd4 100644 --- a/renderer/native/ios/renderer/component/text/HippyShadowText.h +++ b/renderer/native/ios/renderer/component/text/HippyShadowText.h @@ -49,6 +49,7 @@ extern NSAttributedStringKey const HippyShadowViewAttributeName; @property (nonatomic, strong) UIColor *color; @property (nonatomic, copy) NSString *fontFamily; +@property (nonatomic, copy) NSString *fontUrl; @property (nonatomic, assign) CGFloat fontSize; @property (nonatomic, copy) NSString *fontWeight; @property (nonatomic, copy) NSString *fontStyle; @@ -83,6 +84,7 @@ extern NSAttributedStringKey const HippyShadowViewAttributeName; @interface HippyAttributedStringStyleInfo : NSObject @property (nonatomic, strong) NSString *fontFamily; +@property (nonatomic, strong) NSString *fontUrl; @property (nonatomic, strong) NSNumber *fontSize; @property (nonatomic, strong) NSString *fontWeight; @property (nonatomic, strong) NSString *fontStyle; diff --git a/renderer/native/ios/renderer/component/text/HippyShadowText.mm b/renderer/native/ios/renderer/component/text/HippyShadowText.mm index cdf95d7cbb6..bdeef6eb4b0 100644 --- a/renderer/native/ios/renderer/component/text/HippyShadowText.mm +++ b/renderer/native/ios/renderer/component/text/HippyShadowText.mm @@ -476,6 +476,9 @@ - (NSAttributedString *)_attributedStringWithStyleInfo:(HippyAttributedStringSty if (_fontFamily) { styleInfo.fontFamily = _fontFamily; } + if (_fontUrl) { + styleInfo.fontUrl = _fontUrl; + } if (!isnan(_letterSpacing)) { styleInfo.letterSpacing = @(_letterSpacing); } @@ -489,6 +492,7 @@ - (NSAttributedString *)_attributedStringWithStyleInfo:(HippyAttributedStringSty UIFont *font = [HippyFont updateFont:f withFamily:styleInfo.fontFamily + url:styleInfo.fontUrl size:styleInfo.fontSize weight:styleInfo.fontWeight style:styleInfo.fontStyle @@ -881,6 +885,7 @@ -(void)set##setProp : (type)value \ NATIVE_RENDER_TEXT_PROPERTY(AdjustsFontSizeToFit, _adjustsFontSizeToFit, BOOL) NATIVE_RENDER_TEXT_PROPERTY(Color, _color, UIColor *) NATIVE_RENDER_TEXT_PROPERTY(FontFamily, _fontFamily, NSString *) +NATIVE_RENDER_TEXT_PROPERTY(FontUrl, _fontUrl, NSString *) NATIVE_RENDER_TEXT_PROPERTY(FontSize, _fontSize, CGFloat) NATIVE_RENDER_TEXT_PROPERTY(FontWeight, _fontWeight, NSString *) NATIVE_RENDER_TEXT_PROPERTY(FontStyle, _fontStyle, NSString *) diff --git a/renderer/native/ios/renderer/component/text/HippyTextManager.mm b/renderer/native/ios/renderer/component/text/HippyTextManager.mm index edd534a17ea..931dd152ca6 100644 --- a/renderer/native/ios/renderer/component/text/HippyTextManager.mm +++ b/renderer/native/ios/renderer/component/text/HippyTextManager.mm @@ -53,6 +53,7 @@ - (HippyShadowView *)shadowView { HIPPY_EXPORT_SHADOW_PROPERTY(color, UIColor) HIPPY_EXPORT_SHADOW_PROPERTY(fontFamily, NSString) +HIPPY_EXPORT_SHADOW_PROPERTY(fontUrl, NSString) HIPPY_EXPORT_SHADOW_PROPERTY(fontSize, CGFloat) HIPPY_EXPORT_SHADOW_PROPERTY(fontWeight, NSString) HIPPY_EXPORT_SHADOW_PROPERTY(fontStyle, NSString) diff --git a/renderer/native/ios/renderer/component/textinput/HippyBaseTextInput.h b/renderer/native/ios/renderer/component/textinput/HippyBaseTextInput.h index 5b7ac631163..c2c99d18443 100644 --- a/renderer/native/ios/renderer/component/textinput/HippyBaseTextInput.h +++ b/renderer/native/ios/renderer/component/textinput/HippyBaseTextInput.h @@ -32,6 +32,8 @@ @property (nonatomic, strong) NSString *fontStyle; /// Font property - FontFamily @property (nonatomic, strong) NSString *fontFamily; +/// Font property - FontUrl +@property (nonatomic, strong) NSString *fontUrl; @property (nonatomic, strong) UIFont *font; @property (nonatomic, assign) UIEdgeInsets contentInset; diff --git a/renderer/native/ios/renderer/component/textinput/HippyBaseTextInput.m b/renderer/native/ios/renderer/component/textinput/HippyBaseTextInput.m index ff622e02055..28c7a109aff 100644 --- a/renderer/native/ios/renderer/component/textinput/HippyBaseTextInput.m +++ b/renderer/native/ios/renderer/component/textinput/HippyBaseTextInput.m @@ -132,12 +132,21 @@ - (void)setFontFamily:(NSString *)fontFamily { [self setNeedsLayout]; } +- (void)setFontUrl:(NSString *)fontUrl { + _fontUrl = fontUrl; + [self setNeedsLayout]; +} + - (void)rebuildAndUpdateFont { // Convert fontName to fontFamily if needed CGFloat scaleMultiplier = 1.0; // scale not supported NSString *familyName = [HippyFont familyNameWithCSSNameMatching:self.fontFamily]; + if (!familyName) { + familyName = self.fontFamily; + } UIFont *font = [HippyFont updateFont:self.font withFamily:familyName + url:self.fontUrl size:self.fontSize weight:self.fontWeight style:self.fontStyle diff --git a/renderer/native/ios/renderer/component/textinput/HippyShadowTextView.h b/renderer/native/ios/renderer/component/textinput/HippyShadowTextView.h index 7648e2a6bab..cfeb2769444 100644 --- a/renderer/native/ios/renderer/component/textinput/HippyShadowTextView.h +++ b/renderer/native/ios/renderer/component/textinput/HippyShadowTextView.h @@ -42,5 +42,7 @@ @property (nonatomic, strong) NSString *fontStyle; /// Font property - FontFamily @property (nonatomic, strong) NSString *fontFamily; +/// Font property - FontUrl +@property (nonatomic, strong) NSString *fontUrl; @end diff --git a/renderer/native/ios/renderer/component/textinput/HippyShadowTextView.mm b/renderer/native/ios/renderer/component/textinput/HippyShadowTextView.mm index 0d38fba2fc0..bfc00eebb36 100644 --- a/renderer/native/ios/renderer/component/textinput/HippyShadowTextView.mm +++ b/renderer/native/ios/renderer/component/textinput/HippyShadowTextView.mm @@ -226,12 +226,21 @@ - (void)setFontFamily:(NSString *)fontFamily { self.isFontDirty = YES; } +- (void)setFontUrl:(NSString *)fontUrl { + _fontUrl = fontUrl; + self.isFontDirty = YES; +} + - (void)rebuildAndUpdateFont { // Convert fontName to fontFamily if needed CGFloat scaleMultiplier = 1.0; // scale not supported NSString *familyName = [HippyFont familyNameWithCSSNameMatching:self.fontFamily]; + if (!familyName) { + familyName = self.fontFamily; + } UIFont *font = [HippyFont updateFont:self.font withFamily:familyName + url:self.fontUrl size:self.fontSize weight:self.fontWeight style:self.fontStyle diff --git a/renderer/native/ios/renderer/component/textinput/HippyTextViewManager.mm b/renderer/native/ios/renderer/component/textinput/HippyTextViewManager.mm index 7b9bfbf3f1c..e1bf3ebb4fb 100644 --- a/renderer/native/ios/renderer/component/textinput/HippyTextViewManager.mm +++ b/renderer/native/ios/renderer/component/textinput/HippyTextViewManager.mm @@ -144,6 +144,7 @@ - (HippyShadowView *)shadowView { HIPPY_EXPORT_SHADOW_PROPERTY(fontWeight, NSString) HIPPY_EXPORT_SHADOW_PROPERTY(fontStyle, NSString) HIPPY_EXPORT_SHADOW_PROPERTY(fontFamily, NSString) +HIPPY_EXPORT_SHADOW_PROPERTY(fontUrl, NSString) HIPPY_EXPORT_VIEW_PROPERTY(lineHeight, NSNumber) HIPPY_EXPORT_VIEW_PROPERTY(lineSpacing, NSNumber) @@ -177,6 +178,7 @@ - (HippyShadowView *)shadowView { HIPPY_EXPORT_VIEW_PROPERTY(fontWeight, NSString) HIPPY_EXPORT_VIEW_PROPERTY(fontStyle, NSString) HIPPY_EXPORT_VIEW_PROPERTY(fontFamily, NSString) +HIPPY_EXPORT_VIEW_PROPERTY(fontUrl, NSString) - (HippyViewManagerUIBlock)uiBlockToAmendWithShadowView:(HippyShadowView *)hippyShadowView { NSNumber *componentTag = hippyShadowView.hippyTag;