From 1ec89cd1f69776b3cf832412cce4a0fb82d529ea Mon Sep 17 00:00:00 2001 From: arnonchen <745735906@qq.com> Date: Tue, 15 Oct 2024 14:57:42 +0800 Subject: [PATCH 01/20] feat(ios, android): support loading font dynamically --- driver/js/packages/hippy-react/src/index.ts | 2 + .../src/modules/font-loader-module.ts | 35 ++++ driver/js/packages/hippy-react/src/native.ts | 2 + .../src/runtime/native/index.ts | 22 ++ .../src/types/native-modules.ts | 2 + .../native-modules/font-loader-module.ts | 23 +++ .../packages/hippy-vue/src/runtime/native.ts | 14 ++ .../mtt/hippy/bridge/HippyCoreAPI.java | 7 + .../nativemodules/font/FontLoaderModule.java | 46 +++++ .../module/fontLoader/HippyFontLoaderModule.h | 36 ++++ .../fontLoader/HippyFontLoaderModule.mm | 191 ++++++++++++++++++ .../cpp/include/renderer/native_render_jni.h | 4 + .../cpp/src/renderer/native_render_jni.cc | 66 ++++++ .../tencent/mtt/hippy/dom/node/NodeProps.java | 1 + .../hippy/views/textinput/HippyTextInput.java | 20 +- .../textinput/HippyTextInputController.java | 5 + .../com/tencent/renderer/NativeRender.java | 8 + .../renderer/NativeRenderProvider.java | 23 +++ .../com/tencent/renderer/NativeRenderer.java | 19 ++ .../renderer/component/text/FontLoader.java | 157 ++++++++++++++ .../renderer/component/text/TypeFaceUtil.java | 54 +++-- .../renderer/node/TextVirtualNode.java | 19 ++ renderer/native/ios/renderer/HippyFont.h | 1 + renderer/native/ios/renderer/HippyFont.mm | 26 ++- .../renderer/component/text/HippyShadowText.h | 2 + .../component/text/HippyShadowText.mm | 5 + .../component/text/HippyTextManager.mm | 1 + .../component/textinput/HippyBaseTextInput.h | 2 + .../component/textinput/HippyBaseTextInput.m | 9 + .../component/textinput/HippyShadowTextView.h | 2 + .../textinput/HippyShadowTextView.mm | 9 + .../textinput/HippyTextViewManager.mm | 2 + 32 files changed, 793 insertions(+), 22 deletions(-) create mode 100644 driver/js/packages/hippy-react/src/modules/font-loader-module.ts create mode 100644 driver/js/packages/hippy-vue-next/src/types/native-modules/font-loader-module.ts create mode 100644 framework/android/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/font/FontLoaderModule.java create mode 100644 framework/ios/module/fontLoader/HippyFontLoaderModule.h create mode 100644 framework/ios/module/fontLoader/HippyFontLoaderModule.mm create mode 100644 renderer/native/android/src/main/java/com/tencent/renderer/component/text/FontLoader.java 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 f4e5256f91a..701f498853a 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 f7c5a81517a..a2662b23de0 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; From 91c0b2dd0350ffb7b31522a05922695c7b5da5b2 Mon Sep 17 00:00:00 2001 From: arnonchen <745735906@qq.com> Date: Sun, 20 Oct 2024 20:07:23 +0800 Subject: [PATCH 02/20] fix(ios, android): Some fixes for loading font dynamically --- .../nativemodules/font/FontLoaderModule.java | 4 +- .../module/fontLoader/HippyFontLoaderModule.h | 23 ++- .../fontLoader/HippyFontLoaderModule.mm | 160 +++++++++++------- .../cpp/include/renderer/native_render_jni.h | 4 +- .../cpp/src/renderer/native_render_jni.cc | 38 +++-- .../hippy/views/textinput/HippyTextInput.java | 24 ++- .../com/tencent/renderer/NativeRender.java | 3 +- .../renderer/NativeRenderProvider.java | 12 +- .../com/tencent/renderer/NativeRenderer.java | 26 ++- .../renderer/component/text/FontLoader.java | 67 +++++--- .../renderer/component/text/TypeFaceUtil.java | 5 +- .../renderer/node/TextVirtualNode.java | 7 + renderer/native/ios/renderer/HippyFont.mm | 20 +-- 13 files changed, 245 insertions(+), 148 deletions(-) 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 index 6dcde9bacf9..e1755369f2e 100644 --- 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 @@ -34,13 +34,13 @@ public class FontLoaderModule extends HippyNativeModuleBase { public FontLoaderModule(HippyEngineContext context) { super(context); - mFontLoader = new FontLoader(context.getVfsManager()); mNativeRender = NativeRendererManager.getNativeRenderer(context.getRootView().getContext()); + mFontLoader = mNativeRender.getFontLoader(); 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); + mFontLoader.loadAndRefresh(fontFamily, fontUrl, mNativeRender, rootId, promise); } } diff --git a/framework/ios/module/fontLoader/HippyFontLoaderModule.h b/framework/ios/module/fontLoader/HippyFontLoaderModule.h index a87e59f8997..1877330655d 100644 --- a/framework/ios/module/fontLoader/HippyFontLoaderModule.h +++ b/framework/ios/module/fontLoader/HippyFontLoaderModule.h @@ -23,14 +23,27 @@ #import #import "HippyBridgeModule.h" -static NSString *const HippyLoadFontNotification = @"HippyLoadFontNotification"; +NS_ASSUME_NONNULL_BEGIN + +HIPPY_EXTERN NSString *const HippyLoadFontNotification; @interface HippyFontLoaderModule : NSObject -@property (nonatomic, readonly) NSString *fontDir; -@property (nonatomic, readonly) NSString *fontUrlCachePath; +/** + * Get the font file path according to url. + * + * @param url - The url where font file is downloaded + * @return The font file path. Null means the font file has't been downloaded from url. + */ ++ (nullable NSString *)getFontPath:(NSString *)url; -- (NSString *)getFontPath:(NSString *)url; -- (BOOL)registerFontFromURL:(NSString *)urlString error:(NSError *)error; +/** + * Register font files belong to the specific font family if needed. + * + * @param fontFamily - The font family needs to be registered + */ ++ (void)registerFontIfNeeded:(NSString *)fontFamily; @end + +NS_ASSUME_NONNULL_END diff --git a/framework/ios/module/fontLoader/HippyFontLoaderModule.mm b/framework/ios/module/fontLoader/HippyFontLoaderModule.mm index 90cc74c8082..0d5016721d7 100644 --- a/framework/ios/module/fontLoader/HippyFontLoaderModule.mm +++ b/framework/ios/module/fontLoader/HippyFontLoaderModule.mm @@ -30,16 +30,22 @@ #import "HippyUIManager.h" +NSString *const HippyLoadFontNotification = @"HippyLoadFontNotification"; 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; +NSString *const HippyFontDirName = @"HippyFonts"; +NSString *const HippyFontUrlCacheName = @"urlToFile.plist"; +NSString *const HippyFontFamilyCacheName = @"fontFaimilyToFiles.plist"; -@interface HippyFontLoaderModule () { -} - -@end +static NSMutableDictionary *urlToFile; +static NSMutableDictionary *fontFamilyToFiles; +static NSString *fontDirPath; +static NSString *fontUrlCachePath; +static NSString *fontFamilyCachePath; +static NSMutableArray *fontRegistered = [NSMutableArray array]; @implementation HippyFontLoaderModule @@ -49,37 +55,49 @@ @implementation HippyFontLoaderModule - (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]; + [notificationCenter addObserver:self selector:@selector(loadFont:) name:HippyLoadFontNotification object:nil]; + + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + NSString *cachesDirectory = [paths objectAtIndex:0]; + fontDirPath = [cachesDirectory stringByAppendingPathComponent:HippyFontDirName]; + fontUrlCachePath = [fontDirPath stringByAppendingPathComponent:HippyFontUrlCacheName]; + fontFamilyCachePath = [fontDirPath stringByAppendingPathComponent:HippyFontFamilyCacheName]; }); } return self; } -- (void)loadAndRegisterFont:(NSNotification *)notification { - NSLog(@"handle notification"); ++ (void) initDictIfNeeded { + if (fontFamilyToFiles == nil) { + fontFamilyToFiles = [NSMutableDictionary dictionaryWithContentsOfFile:fontFamilyCachePath]; + if (fontFamilyToFiles == nil) { + fontFamilyToFiles = [NSMutableDictionary dictionary]; + } + } + if (urlToFile == nil) { + urlToFile = [NSMutableDictionary dictionaryWithContentsOfFile:fontUrlCachePath]; + if (urlToFile == nil) { + urlToFile = [NSMutableDictionary dictionary]; + } + } +} + +- (void)loadFont:(NSNotification *)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) {}]; + [self load:fontFamily from:urlString resolver:nil rejecter:nil]; } -- (NSString *)getFontPath:(NSString *)url{ - NSMutableDictionary *fontUrlDict = [NSMutableDictionary dictionaryWithContentsOfFile:self.fontUrlCachePath]; - if (fontUrlDict == nil) { - fontUrlDict = [NSMutableDictionary dictionary]; - } - NSString *fontFile = fontUrlDict[url]; ++ (NSString *)getFontPath:(NSString *)url { + [self initDictIfNeeded]; + NSString *fontFile = urlToFile[url]; if (!fontFile) { return nil; } - NSString *fontPath = [self.fontDir stringByAppendingPathComponent:fontFile]; + NSString *fontPath = [fontDirPath stringByAppendingPathComponent:fontFile]; NSFileManager *fileManager = [NSFileManager defaultManager]; if (![fileManager fileExistsAtPath:fontPath]) { return nil; @@ -87,51 +105,70 @@ - (NSString *)getFontPath:(NSString *)url{ 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; ++ (void)registerFontIfNeeded:(NSString *)fontFamily { + [self initDictIfNeeded]; + NSMutableArray *fontFiles = [fontFamilyToFiles objectForKey:fontFamily]; + BOOL isFontRegistered = NO; + if (fontFiles) { + NSMutableArray *fileNotExist = [NSMutableArray array]; + for (NSString *fontFile in fontFiles) { + if (![fontRegistered containsObject:fontFile]) { + NSString *fontFilePath = [fontDirPath stringByAppendingPathComponent:fontFile]; + NSError *error = nil; + if ([self registerFontFromURL:fontFilePath error:&error]) { + [fontRegistered addObject:fontFile]; + isFontRegistered = YES; + HippyLogInfo(@"register font \"%@\" success!", fontFile); + } + else { + if (error.domain == kFontLoaderModuleErrorDomain && error.code == FontLoaderErrorRegisterError) { + [fileNotExist addObject:fontFile]; + } + HippyLogWarn(@"register font \"%@\" fail!", fontFile); + } + } + } + [fontFiles removeObjectsInArray:fileNotExist]; + if (isFontRegistered) { + [[NSNotificationCenter defaultCenter] postNotificationName:HippyFontChangeTriggerNotification object:nil]; } } - return NO; } -- (BOOL)registerFontFromURL:(NSString *)urlString error:(NSError *)error { + ++ (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 + *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); + *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]; +- (void)cacheFontfamily:(NSString *)fontFamily url:(NSString *)url fileName:(NSString *)fileName { + [HippyFontLoaderModule initDictIfNeeded]; + [urlToFile setObject:fileName forKey:url]; + NSMutableArray *fontFiles = [fontFamilyToFiles objectForKey:fontFamily]; + if (!fontFiles) { + fontFiles = [NSMutableArray arrayWithObject:fileName]; + [fontFamilyToFiles setObject:fontFiles forKey:fontFamily]; + } + else { + [fontFiles addObject:fileName]; } - [fontUrlDict setObject:fontFileName forKey:url]; - return [fontUrlDict writeToFile:self.fontUrlCachePath atomically:YES]; + [urlToFile writeToFile:fontUrlCachePath atomically:YES]; + [fontFamilyToFiles writeToFile:fontFamilyCachePath atomically:YES]; } @@ -140,23 +177,26 @@ - (BOOL)cacheFont:(NSString *)fontFileName url:(NSString *)url { NSError *error = [NSError errorWithDomain:kFontLoaderModuleErrorDomain code:FontLoaderErrorUrlError userInfo:@{@"reason": @"url is empty"}]; NSString *errorKey = [NSString stringWithFormat:@"%lu", FontLoaderErrorUrlError]; - reject(errorKey, @"url is empty", error); + if (reject) { + reject(errorKey, @"url is empty", error); + } return; } NSFileManager *fileManager = [NSFileManager defaultManager]; - // Create font directory if not exist - if (![fileManager fileExistsAtPath:self.fontDir]) { + if (![fileManager fileExistsAtPath:fontDirPath]) { NSError *error; - [fileManager createDirectoryAtPath:self.fontDir withIntermediateDirectories:YES attributes:nil error:&error]; + [fileManager createDirectoryAtPath:fontDirPath withIntermediateDirectories:YES attributes:nil error:&error]; if (error) { NSString *errorKey = [NSString stringWithFormat:@"%lu", FontLoaderErrorDirectoryError]; - reject(errorKey, @"directory create error", error); + if (reject) { + reject(errorKey, @"directory create error", error); + } return; } } - NSLog(@"urlString: %@", urlString); + __weak __typeof(self) weakSelf = self; [self.bridge loadContentsAsynchronouslyFromUrl:urlString method:@"Get" params:nil @@ -164,28 +204,24 @@ - (BOOL)cacheFont:(NSString *)fontFileName url:(NSString *)url { queue:nil progress:nil completionHandler:^(NSData *data, NSDictionary *userInfo, NSURLResponse *response, NSError *error) { - NSLog(@"complete:"); + __strong __typeof(weakSelf) strongSelf = weakSelf; if (error) { NSString *errorKey = [NSString stringWithFormat:@"%lu", FontLoaderErrorRequestError]; - NSLog(@"font request error:%@", error.description); - reject(errorKey, @"font request error", error); + if (reject) { + reject(errorKey, @"font request error", error); + } return; } NSString *fileName = [fontFamily stringByAppendingFormat:@".%@", [response.suggestedFilename pathExtension]]; - NSString *fontFilePath = [self.fontDir stringByAppendingPathComponent:fileName]; - NSLog(@"fontFilePath: %@", fontFilePath); + NSString *fontFilePath = [fontDirPath stringByAppendingPathComponent:fileName]; [data writeToFile:fontFilePath atomically:YES]; - [self cacheFont:fileName url:urlString]; - - if ([self registerFontFromURL:fontFilePath error:error]) { - [[NSNotificationCenter defaultCenter] postNotificationName:HippyFontChangeTriggerNotification object:nil]; + [strongSelf cacheFontfamily:fontFamily url:urlString fileName:fileName]; + [[NSNotificationCenter defaultCenter] postNotificationName:HippyFontChangeTriggerNotification object:nil]; + if (resolve) { + HippyLogInfo(@"download font file \"%@\" success!", fileName); 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 9af8185a72d..d920f128cc0 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,9 +41,9 @@ 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 MarkTextNodeDirty(JNIEnv *j_env, jobject j_object, jint j_render_manager_id, jint j_root_id); -void FreshWindow(JNIEnv *j_env, jobject j_object, jint j_render_manager_id, jint j_root_id); +void RefreshWindow(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 f2f10fd4052..44cb4bade6b 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 @@ -78,13 +78,13 @@ REGISTER_JNI("com/tencent/renderer/NativeRenderProvider", UpdateRootSize) REGISTER_JNI("com/tencent/renderer/NativeRenderProvider", - "freshWindow", + "refreshWindow", "(II)V", - FreshWindow) + RefreshWindow) REGISTER_JNI("com/tencent/renderer/NativeRenderProvider", "markTextNodeDirty", - "(I)V", + "(II)V", MarkTextNodeDirty) static jint JNI_OnLoad(__unused JavaVM* j_vm, __unused void* reserved) { @@ -173,29 +173,45 @@ void MarkTextNodeDirtyRecursive(const std::shared_ptr& node) { } } -void MarkTextNodeDirty(JNIEnv *j_env, jobject j_object, jint j_root_id) { +void MarkTextNodeDirty(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) << "MarkTextNodeDirty j_render_manager_id invalid"; + return; + } + std::shared_ptr dom_manager = render_manager->GetDomManager(); + if (dom_manager == nullptr) { + FOOTSTONE_DLOG(WARNING) << "MarkTextNodeDirty 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); - bool ret = root_map.Find(root_id, root_node); + ret = root_map.Find(root_id, root_node); if (!ret) { - FOOTSTONE_DLOG(WARNING) << "root_node is nullptr"; + FOOTSTONE_DLOG(WARNING) << "MarkTextNodeDirty root_node is nullptr"; return; } - MarkTextNodeDirtyRecursive(root_node); + + std::vector> ops = {[root_node] { + MarkTextNodeDirtyRecursive(root_node); + }}; + dom_manager->PostTask(Scene(std::move(ops))); } -void FreshWindow(JNIEnv *j_env, jobject j_object, jint j_render_manager_id, jint j_root_id) { +void RefreshWindow(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"; + FOOTSTONE_DLOG(WARNING) << "RefreshWindow 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"; + FOOTSTONE_DLOG(WARNING) << "RefreshWindow dom_manager is nullptr"; return; } auto& root_map = RootNode::PersistentMap(); @@ -203,7 +219,7 @@ void FreshWindow(JNIEnv *j_env, jobject j_object, jint j_render_manager_id, jint 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"; + FOOTSTONE_DLOG(WARNING) << "RefreshWindow root_node is nullptr"; return; } 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 660dea5cb08..380d56913b8 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 @@ -16,8 +16,6 @@ package com.tencent.mtt.hippy.views.textinput; -import static com.tencent.mtt.hippy.views.textinput.HippyTextInputController.UNSET; - import android.annotation.SuppressLint; import android.content.Context; import android.graphics.BlendMode; @@ -43,8 +41,10 @@ import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.TextView; + import androidx.annotation.Nullable; import androidx.appcompat.widget.AppCompatEditText; + import com.tencent.mtt.hippy.common.HippyMap; import com.tencent.mtt.hippy.uimanager.HippyViewBase; import com.tencent.mtt.hippy.uimanager.NativeGestureDispatcher; @@ -59,6 +59,7 @@ import com.tencent.renderer.component.text.TypeFaceUtil; import com.tencent.renderer.node.RenderNode; import com.tencent.renderer.utils.EventUtils; + import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; @@ -99,6 +100,8 @@ public class HippyTextInput extends AppCompatEditText implements HippyViewBase, private String mFontFamily; private String mFontUrl; private Paint mTextPaint; + protected FontLoader mFontLoader; + protected boolean mFromFontLoader = false; public HippyTextInput(Context context) { super(context); @@ -106,6 +109,10 @@ public HippyTextInput(Context context) { setFocusableInTouchMode(true); setOverScrollMode(View.OVER_SCROLL_IF_CONTENT_SCROLLS); + NativeRender nativeRenderer = NativeRendererManager.getNativeRenderer(context); + if (nativeRenderer != null) { + mFontLoader = nativeRenderer.getFontLoader(); + } mDefaultGravityHorizontal = getGravity() & (Gravity.HORIZONTAL_GRAVITY_MASK | Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK); @@ -208,8 +215,13 @@ public void setTextLineHeight(int lineHeight) { } public void onBatchComplete() { + if (!mFromFontLoader && mFontLoader != null && mFontLoader.isFontLoaded(mFontFamily)) { + mShouldUpdateTypeface = true; + mFromFontLoader = true; + } if (mShouldUpdateTypeface) { updateTypeface(); + mShouldUpdateTypeface = false; } if (!mShouldUpdateLineHeight) { return; @@ -760,6 +772,9 @@ public void setFontFamily(String family) { if (!Objects.equals(mFontFamily, family)) { mFontFamily = family; mShouldUpdateTypeface = true; + if (mFromFontLoader && mFontLoader != null && !mFontLoader.isFontLoaded(mFontFamily)) { + mFromFontLoader = false; + } } } @@ -788,12 +803,9 @@ private void updateTypeface() { FontLoader loader = nativeRenderer == null ? null : nativeRenderer.getFontLoader(); if (loader != null) { int rootId = nativeRenderer.getRootView(this).getId(); - mShouldUpdateTypeface = loader.loadIfNeeded(mFontFamily, mFontUrl, nativeRenderer, rootId); + 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/renderer/NativeRender.java b/renderer/native/android/src/main/java/com/tencent/renderer/NativeRender.java index 701f498853a..b902f7bfe80 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 @@ -53,7 +53,6 @@ public interface NativeRender extends RenderExceptionHandler, RenderLogHandler { @Nullable ImageLoaderAdapter getImageLoader(); - @Nullable FontLoader getFontLoader(); @Nullable @@ -111,7 +110,7 @@ VirtualNode createVirtualNode(int rootId, int id, int pid, int index, @NonNull S void markTextNodeDirty(int rootId); - void freshWindow(int rootId); + void refreshWindow(int rootId); void updateDimension(int width, int height); 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 f31df01578b..fc2b89cc074 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,8 +337,12 @@ 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 refreshWindow(int rootId) { + refreshWindow(mInstanceId, rootId); + } + + public void markTextNodeDirty(int rootId) { + markTextNodeDirty(mInstanceId, rootId); } public void onSizeChanged(int rootId, int nodeId, int width, int height, boolean isSync) { @@ -445,7 +449,7 @@ private void dispatchEventImpl(int rootId, int nodeId, @NonNull String eventName * @param instanceId the unique id of native (C++) render manager */ @SuppressWarnings("JavaJniMissingFunction") - private native void freshWindow(int instanceId, int rootId); + private native void refreshWindow(int instanceId, int rootId); /** * Call back from Android system when size changed, just like horizontal and vertical screen @@ -454,7 +458,7 @@ private void dispatchEventImpl(int rootId, int nodeId, @NonNull String eventName * @param rootId the root node id */ @SuppressWarnings("JavaJniMissingFunction") - public native void markTextNodeDirty(int rootId); + public native void markTextNodeDirty(int instanceId, int rootId); /** * Updates the size to the specified node, such as modal node, should set new window size before 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 a2662b23de0..5e285c1843d 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 @@ -20,8 +20,8 @@ import static com.tencent.mtt.hippy.dom.node.NodeProps.PADDING_LEFT; import static com.tencent.mtt.hippy.dom.node.NodeProps.PADDING_RIGHT; import static com.tencent.mtt.hippy.dom.node.NodeProps.PADDING_TOP; -import static com.tencent.renderer.NativeRenderException.ExceptionCode.UI_TASK_QUEUE_ADD_ERR; import static com.tencent.renderer.NativeRenderException.ExceptionCode.INVALID_NODE_DATA_ERR; +import static com.tencent.renderer.NativeRenderException.ExceptionCode.UI_TASK_QUEUE_ADD_ERR; import static com.tencent.renderer.NativeRenderException.ExceptionCode.UI_TASK_QUEUE_UNAVAILABLE_ERR; import android.content.Context; @@ -29,12 +29,14 @@ import android.text.Layout; import android.view.View; import android.view.ViewGroup; - import android.view.ViewParent; + import androidx.annotation.MainThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.tencent.mtt.hippy.HippyInstanceLifecycleEventListener; +import com.tencent.mtt.hippy.HippyRootView; import com.tencent.mtt.hippy.common.BaseEngineContext; import com.tencent.mtt.hippy.common.Callback; import com.tencent.mtt.hippy.common.LogAdapter; @@ -42,6 +44,8 @@ import com.tencent.mtt.hippy.serialization.nio.reader.SafeHeapReader; import com.tencent.mtt.hippy.serialization.nio.writer.SafeHeapWriter; import com.tencent.mtt.hippy.serialization.string.InternalizedStringTable; +import com.tencent.mtt.hippy.uimanager.RenderManager; +import com.tencent.mtt.hippy.utils.LogUtils; import com.tencent.mtt.hippy.utils.PixelUtil; import com.tencent.mtt.hippy.utils.UIThreadUtils; import com.tencent.mtt.hippy.views.image.HippyImageViewController; @@ -58,23 +62,24 @@ import com.tencent.renderer.node.TextRenderNode; import com.tencent.renderer.node.VirtualNode; import com.tencent.renderer.node.VirtualNodeManager; - import com.tencent.renderer.serialization.Deserializer; import com.tencent.renderer.serialization.Serializer; import com.tencent.renderer.utils.ArrayUtils; import com.tencent.renderer.utils.ChoreographerUtils; import com.tencent.renderer.utils.DisplayUtils; import com.tencent.renderer.utils.EventUtils.EventType; - +import com.tencent.renderer.utils.FlexUtils; +import com.tencent.renderer.utils.FlexUtils.FlexMeasureMode; import com.tencent.renderer.utils.MapUtils; import com.tencent.vfs.VfsManager; + import java.lang.ref.WeakReference; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; -import java.util.Map; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.concurrent.BlockingQueue; @@ -82,13 +87,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; - -import com.tencent.mtt.hippy.utils.LogUtils; -import com.tencent.mtt.hippy.HippyInstanceLifecycleEventListener; -import com.tencent.mtt.hippy.HippyRootView; -import com.tencent.mtt.hippy.uimanager.RenderManager; -import com.tencent.renderer.utils.FlexUtils; -import com.tencent.renderer.utils.FlexUtils.FlexMeasureMode; import java.util.concurrent.atomic.AtomicInteger; public class NativeRenderer extends Renderer implements NativeRender, NativeRenderDelegate { @@ -421,8 +419,8 @@ public void markTextNodeDirty(int rootId) { mRenderProvider.markTextNodeDirty(rootId); } - public void freshWindow(int rootId) { - mRenderProvider.freshWindow(rootId); + public void refreshWindow(int rootId) { + mRenderProvider.refreshWindow(rootId); } @Override 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 index 58a8d9f442b..c0bb3cc8498 100644 --- 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 @@ -36,6 +36,7 @@ import java.io.ObjectOutputStream; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; public class FontLoader { @@ -43,27 +44,26 @@ public class FontLoader { private final File mFontDir; private final File mFontUrlDictPath; private HashMap mFontUrlDict; - private static final String[] allowedExtensions = {"otf", "ttf"}; + private HashSet mFontsLoaded; + private static final String[] ALLOWED_EXTENSIONS = {"otf", "ttf"}; + private static final String FONT_DIR_NAME = "HippyFonts"; + private static final String FONT_URL_DICT_NAME = "fontUrlDict.ser"; + 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<>(); - } + mFontDir = new File(ContextHolder.getAppContext().getCacheDir(), FONT_DIR_NAME); + mFontUrlDictPath = new File(mFontDir, FONT_URL_DICT_NAME); + mFontsLoaded = new HashSet<>(); } - private void saveFontFile(byte[] byteArray, String fileName, Promise promise) { + private boolean saveFontFile(byte[] byteArray, String fileName, Promise promise) { if (!mFontDir.exists()) { if (!mFontDir.mkdirs()) { if (promise != null) { promise.reject("Create font directory failed"); } - return; + return false; } } File fontFile = new File(mFontDir, fileName); @@ -72,20 +72,26 @@ private void saveFontFile(byte[] byteArray, String fileName, Promise promise) { if (promise != null) { promise.resolve(null); } + return true; } catch (IOException e) { if (promise != null) { promise.reject("Write font file failed:" + e.getMessage()); } + return false; } } + public boolean isFontLoaded(String fontFamily) { + return mFontsLoaded.contains(fontFamily); + } + private void saveFontUrlDictFile() { try (FileOutputStream fos = new FileOutputStream(mFontUrlDictPath); ObjectOutputStream oos = new ObjectOutputStream(fos)) { oos.writeObject(mFontUrlDict); - LogUtils.d("FontLoader", "save fontUrlDict.ser success"); + LogUtils.d("FontLoader", "Save fontUrlDict.ser success"); } catch (IOException e) { - LogUtils.d("FontLoader", "save fontUrlDict.ser failed"); + LogUtils.d("FontLoader", "Save fontUrlDict.ser failed"); } } @@ -93,7 +99,7 @@ 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)) { + if (Arrays.asList(ALLOWED_EXTENSIONS).contains(ext)) { return "." + ext; } } @@ -102,6 +108,14 @@ public static String getFileExtension(String url) { public boolean loadIfNeeded(final String fontFamily, final String fontUrl, NativeRender render, int rootId) { + if (mFontUrlDict == null) { + try (FileInputStream fis = new FileInputStream(mFontUrlDictPath); + ObjectInputStream ois = new ObjectInputStream(fis)) { + mFontUrlDict = (HashMap) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + mFontUrlDict = new HashMap<>(); + } + } String fontFileName = mFontUrlDict.get(fontUrl); if (fontFileName != null) { File fontFile = new File(mFontDir, fontFileName); @@ -109,14 +123,14 @@ public boolean loadIfNeeded(final String fontFamily, final String fontUrl, Nativ return false; } } - loadAndFresh(fontFamily, fontUrl, render, rootId, null); + loadAndRefresh(fontFamily, fontUrl, render, rootId, null); return true; } - public void loadAndFresh(final String fontFamily, final String fontUrl, NativeRender render, + public void loadAndRefresh(final String fontFamily, final String fontUrl, NativeRender render, int rootId, Promise promise) { - LogUtils.d("FontLoader", "start load" + fontUrl); + LogUtils.d("FontLoader", "Start load" + fontUrl); if (TextUtils.isEmpty(fontUrl)) { if (promise != null) { promise.reject("Url parameter is empty!"); @@ -131,19 +145,20 @@ public void onFetchCompleted(@NonNull final ResourceDataHolder dataHolder) { 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); + promise.reject("Fetch font file failed, url=" + fontUrl); } } else { String fileName = fontFamily + getFileExtension(fontUrl); - saveFontFile(bytes, fileName, promise); - mFontUrlDict.put(fontUrl, fileName); - saveFontUrlDictFile(); - TypeFaceUtil.clearFontCache(fontFamily); - render.markTextNodeDirty(rootId); - render.freshWindow(rootId); + if (saveFontFile(bytes, fileName, promise)) { + LogUtils.d("FontLoader", "Fetch font file success"); + mFontsLoaded.add(fontFamily); + mFontUrlDict.put(fontUrl, fileName); + saveFontUrlDictFile(); + TypeFaceUtil.clearFontCache(fontFamily); + render.markTextNodeDirty(rootId); + render.refreshWindow(rootId); + } } dataHolder.recycle(); } 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 6e49a88ee4b..0dd8b93ea97 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 @@ -21,9 +21,8 @@ import android.os.Build.VERSION; 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; @@ -43,7 +42,7 @@ public class TypeFaceUtil { 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 FONTS_PATH = "fonts/"; + private static final String FONTS_PATH = "HippyFonts/"; private static final Map> sFontCache = new HashMap<>(); /** 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 49e9304d2ad..5853640e5dc 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 @@ -41,6 +41,7 @@ import com.tencent.mtt.hippy.annotation.HippyControllerProps; import com.tencent.mtt.hippy.dom.node.NodeProps; import com.tencent.mtt.hippy.utils.I18nUtil; +import com.tencent.mtt.hippy.utils.LogUtils; import com.tencent.mtt.hippy.utils.PixelUtil; import com.tencent.renderer.NativeRender; import com.tencent.renderer.component.text.FontAdapter; @@ -114,6 +115,7 @@ public class TextVirtualNode extends VirtualNode { protected String mFontUrl; @Nullable protected FontLoader mFontLoader; + protected boolean mFromFontLoader = false; @Nullable protected SpannableStringBuilder mSpanned; @Nullable @@ -193,6 +195,7 @@ public void setFontFamily(String family) { public void setFontUrl(String fontUrl) { if (!Objects.equals(mFontUrl, fontUrl)) { mFontUrl = fontUrl; + LogUtils.d("TextVirtualNode", "fontUrl:"+fontUrl); markDirty(); } } @@ -554,6 +557,10 @@ protected Layout createLayout() { @NonNull protected Layout createLayout(final float width, final FlexMeasureMode widthMode) { + if (!mFromFontLoader && mFontLoader != null && mFontLoader.isFontLoaded(mFontFamily)) { + mDirty = true; + mFromFontLoader = true; + } if (mSpanned == null || mDirty) { mSpanned = createSpan(true); mDirty = false; diff --git a/renderer/native/ios/renderer/HippyFont.mm b/renderer/native/ios/renderer/HippyFont.mm index af0c362a9e4..3093a042f36 100644 --- a/renderer/native/ios/renderer/HippyFont.mm +++ b/renderer/native/ios/renderer/HippyFont.mm @@ -207,16 +207,8 @@ + (UIFont *)updateFont:(UIFont *)font 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 { + NSString *fontPath = [HippyFontLoaderModule getFontPath:url]; + if (!fontPath) { NSDictionary *userInfo = @{@"fontUrl": url, @"fontFamily": family}; [[NSNotificationCenter defaultCenter] postNotificationName:HippyLoadFontNotification object:nil userInfo:userInfo]; } @@ -251,11 +243,17 @@ + (UIFont *)updateFont:(UIFont *)font if (scaleMultiplier > 0.0 && scaleMultiplier != 1.0) { fontSize = round(fontSize * scaleMultiplier); } - familyName = [HippyConvert NSString:family] ?: familyName; + if ([HippyConvert NSString:family] && family.length != 0) { + familyName = family; + } isItalic = style ? [HippyConvert NativeRenderFontStyle:style] : isItalic; fontWeight = weight ? [HippyConvert NativeRenderFontWeight:weight] : fontWeight; BOOL didFindFont = NO; + + if (fontNamesForFamilyName(familyName).count == 0) { + [HippyFontLoaderModule registerFontIfNeeded:familyName]; + } // Handle system font as special case. This ensures that we preserve // the specific metrics of the standard system font as closely as possible. From 42538c1d3e0e7654b9005f1ecc5bcbf8f76f58ab Mon Sep 17 00:00:00 2001 From: arnonchen <745735906@qq.com> Date: Thu, 17 Oct 2024 12:01:46 +0800 Subject: [PATCH 03/20] feat(demo): add demo for dynamically load font --- .../src/modules/FontLoader/index.jsx | 140 ++++++++++++++++++ .../hippy-react-demo/src/modules/index.js | 1 + .../examples/hippy-react-demo/src/routes.js | 8 + .../native-demos/demo-vue-native.vue | 70 ++++++++- .../native-demo/demo-vue-native.vue | 78 +++++++++- .../src/runtime/native/index.ts | 4 +- 6 files changed, 293 insertions(+), 8 deletions(-) create mode 100644 driver/js/examples/hippy-react-demo/src/modules/FontLoader/index.jsx diff --git a/driver/js/examples/hippy-react-demo/src/modules/FontLoader/index.jsx b/driver/js/examples/hippy-react-demo/src/modules/FontLoader/index.jsx new file mode 100644 index 00000000000..4828686db3a --- /dev/null +++ b/driver/js/examples/hippy-react-demo/src/modules/FontLoader/index.jsx @@ -0,0 +1,140 @@ +import React from 'react'; +import { + View, + ScrollView, + StyleSheet, + Text, + TextInput, + FontLoaderModule, +} from '@hippy/react'; + +const styles = StyleSheet.create({ + itemTitle: { + alignItems: 'flex-start', + justifyContent: 'center', + height: 40, + borderWidth: 1, + borderStyle: 'solid', + borderColor: '#e0e0e0', + borderRadius: 2, + backgroundColor: '#fafafa', + padding: 10, + marginTop: 10, + }, + wrapper: { + borderColor: '#eee', + borderWidth: 1, + borderStyle: 'solid', + paddingHorizontal: 10, + paddingVertical: 5, + marginVertical: 10, + flexDirection: 'column', + justifyContent: 'flex-start', + alignItems: 'flex-start', + }, + infoContainer: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'flex-start', + marginTop: 5, + marginBottom: 5, + flexWrap: 'wrap', + }, + text_style: { + fontSize: 16, + }, + input_url_style: { + height: 60, + marginVertical: 10, + fontSize: 16, + color: '#242424', + }, + input_font_style: { + height: 30, + marginVertical: 10, + fontSize: 16, + color: '#242424', + }, +}); + +export default class LoadFontExample extends React.Component { + constructor(props) { + super(props); + this.state = { + fontFamily: '', + inputFontFamily: '', + fontUrl: '', + loadState: '', + }; + } + + fillExample() { + this.setState({ inputFontFamily: 'HYHuaXianZi J' }); + this.setState({ fontUrl: 'https://zf.sc.chinaz.com/Files/DownLoad/upload/2024/1009/hanyihuaxianzijianti.ttf' }); + } + + async loadFont() { + this.setState({ fontFamily: this.state.inputFontFamily }); + await FontLoaderModule.load(this.state.fontFamily, this.state.fontUrl) + .then(() => { + this.setState({ loadState: 'Success' }); + }) + .catch((error) => { + this.setState({ loadState: error }); + }); + } + + render() { + return ( + + + 通过组件fontUrl属性动态下载并使用字体 + + + This sentence will use font 'HYHuaXianZi F' downloaded dynamically according to 'fontUrl' property. + + + 这句话将使用通过fontUrl属性下载的汉仪花仙子繁体字体. + + + 下载并使用字体 + + + This sentence will be set the specific font after download. + + + 这句话将用指定的下载字体显示。 + + this.setState({inputFontFamily: text})} + /> + this.setState({fontUrl: text})} + /> + + + this.fillExample()}> + 填充示例 + + this.loadFont()}> + 下载字体 + + + + load state: {this.state.loadState} + + ); + } +} diff --git a/driver/js/examples/hippy-react-demo/src/modules/index.js b/driver/js/examples/hippy-react-demo/src/modules/index.js index 6ff316a0c40..37ae85f39da 100644 --- a/driver/js/examples/hippy-react-demo/src/modules/index.js +++ b/driver/js/examples/hippy-react-demo/src/modules/index.js @@ -5,3 +5,4 @@ export { default as AsyncStorage } from './AsyncStorage'; export { default as NetInfo } from './NetInfo'; export { default as WebSocket } from './WebSocket'; export { default as UIManagerModule } from './UIManagerModule'; +export { default as FontLoader } from './FontLoader'; diff --git a/driver/js/examples/hippy-react-demo/src/routes.js b/driver/js/examples/hippy-react-demo/src/routes.js index f829521fd03..e7b0cfa2c4b 100644 --- a/driver/js/examples/hippy-react-demo/src/routes.js +++ b/driver/js/examples/hippy-react-demo/src/routes.js @@ -184,6 +184,14 @@ export default [ type: Type.MODULE, }, }, + { + path: '/FontLoader', + name: 'FontLoader 模块', + component: PAGE_LIST.FontLoader, + meta: { + type: Type.MODULE, + }, + }, { path: '/Others', name: 'Others', diff --git a/driver/js/examples/hippy-vue-demo/src/components/native-demos/demo-vue-native.vue b/driver/js/examples/hippy-vue-demo/src/components/native-demos/demo-vue-native.vue index 74b9e686359..aa1c99eb72d 100644 --- a/driver/js/examples/hippy-vue-demo/src/components/native-demos/demo-vue-native.vue +++ b/driver/js/examples/hippy-vue-demo/src/components/native-demos/demo-vue-native.vue @@ -248,6 +248,48 @@ + +
+ + + This sentence will be set the specific font after download. + + + 这句话将用指定的下载字体显示。 + + + +
+ + +
+ load state: {{ loadState }} +
+
{ this.netInfoText = `收到通知: ${info.network_info}`; }); - fetch('https://hippyjs.org', { + fetch('https://openhippy.com/', { mode: 'no-cors', // 2.14.0 or above supports other options(not only method/headers/url/body) }).then((responseJson) => { this.fetchText = `成功状态: ${responseJson.status}`; @@ -402,12 +448,30 @@ export default { console.log('ImageLoader getSize', result); this.imageSize = `${result.width}x${result.height}`; }, + async load() { + this.fontFamily = this.inputFontFaimly; + console.log('fontFamily:', this.fontFamily) + console.log('fontUrl:', this.fontUrl) + let result; + try { + await Vue.Native.FontLoader.load(this.fontFamily, this.fontUrl); + result = 'success'; + } catch (error) { + result = error.message; + } + this.loadState = result; + }, + fillExample() { + this.inputFontFaimly = 'HYHuaXianZi J'; + this.fontUrl = 'https://zf.sc.chinaz.com/Files/DownLoad/upload/2024/1009/hanyihuaxianzijianti.ttf'; + }, + setCookie() { - Vue.Native.Cookie.set('https://hippyjs.org', 'name=hippy;network=mobile'); + Vue.Native.Cookie.set('https://openhippy.com/', 'name=hippy;network=mobile'); this.cookieString = '\'name=hippy;network=mobile\' is set'; }, getCookie() { - Vue.Native.Cookie.getAll('https://hippyjs.org').then((cookies) => { + Vue.Native.Cookie.getAll('https://openhippy.com/').then((cookies) => { this.cookiesValue = cookies; }); }, diff --git a/driver/js/examples/hippy-vue-next-demo/src/components/native-demo/demo-vue-native.vue b/driver/js/examples/hippy-vue-next-demo/src/components/native-demo/demo-vue-native.vue index 6405d34d68e..e1fad9a0ab4 100644 --- a/driver/js/examples/hippy-vue-next-demo/src/components/native-demo/demo-vue-native.vue +++ b/driver/js/examples/hippy-vue-next-demo/src/components/native-demo/demo-vue-native.vue @@ -249,6 +249,50 @@
+ +
+ + + This sentence will be set the specific font after download. + + + 这句话将用指定的下载字体显示。 + + + +
+ + +
+ load state: {{ loadState }} +
+
@@ -337,6 +381,10 @@ export default defineComponent({ const cookieString = ref('ready to set'); const cookiesValue = ref(''); const eventTriggeredTimes = ref(0); + const inputFontFaimly = ref(''); + const fontUrl = ref(''); + const fontFamily = ref(''); + const loadState = ref(''); /** * set local storage @@ -375,12 +423,30 @@ export default defineComponent({ imageSize.value = `${result.width}x${result.height}`; }; + const load = async () => { + fontFamily.value = inputFontFaimly.value; + console.log('load fontFamily:', fontFamily.value) + console.log('load fontUrl:', fontUrl.value) + let result; + try { + await Native.FontLoader.load(fontFamily.value, fontUrl.value); + result = 'success'; + } catch (error) { + result = error.message; + } + loadState.value = result; + }; + const fillExample = () => { + inputFontFaimly.value = 'HYHuaXianZi J'; + fontUrl.value = 'https://zf.sc.chinaz.com/Files/DownLoad/upload/2024/1009/hanyihuaxianzijianti.ttf'; + }; + const setCookie = () => { - Native.Cookie.set('https://hippyjs.org', 'name=hippy;network=mobile'); + Native.Cookie.set('https://openhippy.com/', 'name=hippy;network=mobile'); cookieString.value = '\'name=hippy;network=mobile\' is set'; }; const getCookie = () => { - Native.Cookie.getAll('https://hippyjs.org').then((cookies) => { + Native.Cookie.getAll('https://openhippy.com/').then((cookies) => { cookiesValue.value = cookies; }); }; @@ -412,7 +478,7 @@ export default defineComponent({ netInfoText.value = `收到通知: ${info.network_info}`; }); - fetch('https://hippyjs.org', { + fetch('https://openhippy.com/', { mode: 'no-cors', // 2.14.0 or above supports other options(not only method/headers/url/body) }) .then((responseJson) => { @@ -441,6 +507,8 @@ export default defineComponent({ cookieString, cookiesValue, getSize, + load, + fillExample, setItem, getItem, removeItem, @@ -449,6 +517,10 @@ export default defineComponent({ getBoundingClientRect, triggerAppEvent, eventTriggeredTimes, + inputFontFaimly, + fontUrl, + fontFamily, + loadState, }; }, beforeDestroy() { 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 d90df8a361c..c6eaba00f19 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 @@ -523,10 +523,10 @@ export const Native: NativeApiType = { FontLoader: { /** - * get image size before image rendering + * Download the font from the url. * * @param fontFamily - * @param url - image url + * @param url - font url */ load(fontFamily, url): Promise { return Native.callNativeWithPromise.call( From 1ce1cb27a9c836c3a0e5fabb781595eb89efe964 Mon Sep 17 00:00:00 2001 From: arnonchen <745735906@qq.com> Date: Mon, 21 Oct 2024 11:09:17 +0800 Subject: [PATCH 04/20] fix: fix the "given a font name rather than font family" condition --- renderer/native/ios/renderer/HippyFont.mm | 4 ++-- .../ios/renderer/component/textinput/HippyBaseTextInput.m | 6 +----- .../ios/renderer/component/textinput/HippyShadowTextView.mm | 6 +----- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/renderer/native/ios/renderer/HippyFont.mm b/renderer/native/ios/renderer/HippyFont.mm index 3093a042f36..f242584a610 100644 --- a/renderer/native/ios/renderer/HippyFont.mm +++ b/renderer/native/ios/renderer/HippyFont.mm @@ -208,7 +208,7 @@ + (UIFont *)updateFont:(UIFont *)font // Defaults if (url) { NSString *fontPath = [HippyFontLoaderModule getFontPath:url]; - if (!fontPath) { + if (!fontPath && family) { NSDictionary *userInfo = @{@"fontUrl": url, @"fontFamily": family}; [[NSNotificationCenter defaultCenter] postNotificationName:HippyLoadFontNotification object:nil userInfo:userInfo]; } @@ -280,7 +280,7 @@ + (UIFont *)updateFont:(UIFont *)font // Gracefully handle being given a font name rather than font family, for // example: "Helvetica Light Oblique" rather than just "Helvetica". if (!didFindFont && familyName.length > 0 && fontNamesForFamilyName(familyName).count == 0) { - familyName = font.familyName; + familyName = [HippyFont familyNameWithCSSNameMatching:familyName] ?: familyName; fontWeight = weight ? fontWeight : weightOfFont(font); isItalic = style ? isItalic : isItalicFont(font); isCondensed = isCondensedFont(font); diff --git a/renderer/native/ios/renderer/component/textinput/HippyBaseTextInput.m b/renderer/native/ios/renderer/component/textinput/HippyBaseTextInput.m index 28c7a109aff..a1bdc9f20d1 100644 --- a/renderer/native/ios/renderer/component/textinput/HippyBaseTextInput.m +++ b/renderer/native/ios/renderer/component/textinput/HippyBaseTextInput.m @@ -140,12 +140,8 @@ - (void)setFontUrl:(NSString *)fontUrl { - (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 + withFamily:self.fontFamily url:self.fontUrl size:self.fontSize weight:self.fontWeight diff --git a/renderer/native/ios/renderer/component/textinput/HippyShadowTextView.mm b/renderer/native/ios/renderer/component/textinput/HippyShadowTextView.mm index bfc00eebb36..fa432c48a62 100644 --- a/renderer/native/ios/renderer/component/textinput/HippyShadowTextView.mm +++ b/renderer/native/ios/renderer/component/textinput/HippyShadowTextView.mm @@ -234,12 +234,8 @@ - (void)setFontUrl:(NSString *)fontUrl { - (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 + withFamily:self.fontFamily url:self.fontUrl size:self.fontSize weight:self.fontWeight From 455118cae75dc3d4b3af5afc31c999a1d4cbe3fe Mon Sep 17 00:00:00 2001 From: arnonchen <745735906@qq.com> Date: Thu, 31 Oct 2024 14:17:14 +0800 Subject: [PATCH 05/20] feat(doc): add FontLoaderModule --- docs/api/hippy-react/modules.md | 16 ++++++++++++++++ docs/api/hippy-vue/vue-native.md | 15 +++++++++++++++ docs/api/style/appearance.md | 8 ++++++++ 3 files changed, 39 insertions(+) diff --git a/docs/api/hippy-react/modules.md b/docs/api/hippy-react/modules.md index 67acc1194d0..042ac44aaf8 100644 --- a/docs/api/hippy-react/modules.md +++ b/docs/api/hippy-react/modules.md @@ -281,6 +281,22 @@ AsyncStorage 是一个简单的、异步的、持久化的 Key-Value 存储系 --- +# FontLoaderModule + +提供通过url动态下载远程字体的能力,下载的字体将保存在应用Cache目录下,由Hippy统一管理,可能被终端系统删除。常用字体不推荐使用该模块动态下载。 + + +## 方法 + +### FontLoaderModule.load + +`(fontFamily: string, fontUrl: string) => Promise` 通过fontUrl异步下载字体,下载完成后会刷新终端文本。 + +> - fontFamily - 下载字体的字体家族,用于保存文件 +> - fontUrl - 下载字体的地址 + +--- + # NetInfo [[NetInfo 范例]](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo/src/modules/NetInfo) diff --git a/docs/api/hippy-vue/vue-native.md b/docs/api/hippy-vue/vue-native.md index da43ca625b4..de336f0ed20 100644 --- a/docs/api/hippy-vue/vue-native.md +++ b/docs/api/hippy-vue/vue-native.md @@ -290,6 +290,21 @@ console.log(Vue.Native.getElemCss(this.demon1Point)) // => { height: 80, left: 0 --- +# FontLoaderModule + +提供通过url动态下载远程字体的能力,下载的字体将保存在应用Cache目录下,由Hippy统一管理,可能被终端系统删除。常用字体不推荐使用该模块动态下载。 + +## 方法 + +### FontLoader.load + +`(fontFamily: string, fontUrl: string) => Promise` 通过fontUrl异步下载字体,下载完成后会刷新终端文本。 + +> * fontFamily - 下载字体的字体家族,用于保存文件 +> * fontUrl - 下载字体的地址 + +--- + # ImageLoader 通过该模块可以对远程图片进行相应操作 diff --git a/docs/api/style/appearance.md b/docs/api/style/appearance.md index e96d56b7f03..e99a2106658 100644 --- a/docs/api/style/appearance.md +++ b/docs/api/style/appearance.md @@ -164,6 +164,14 @@ | ------------------------ | ---- | ------------ | | enum('normal', 'italic') | 否 | Android、iOS | +# fontUrl + +远程字体的url,会在渲染时异步动态下载 + +| 类型 | 必需 | 支持平台 | +| ------------------------ | ---- | ------------ | +| string | 否 | Android、iOS | + # fontWeight 字体粗细 From 02cf5d67b1ab9b781777a9d8b287f39e2e06c9b6 Mon Sep 17 00:00:00 2001 From: arnonchen <745735906@qq.com> Date: Mon, 21 Oct 2024 21:06:59 +0800 Subject: [PATCH 06/20] fix(android): improve the implemention of loading font dynamically --- .../openhippy/connector/NativeRenderer.java | 12 ++++ .../openhippy/connector/RenderConnector.java | 4 ++ .../tencent/mtt/hippy/HippyEngineContext.java | 3 + .../mtt/hippy/HippyEngineManagerImpl.java | 2 +- .../nativemodules/font/FontLoaderModule.java | 11 +--- .../cpp/include/renderer/native_render_jni.h | 3 +- .../cpp/src/renderer/native_render_jni.cc | 46 ++----------- .../hippy/views/textinput/HippyTextInput.java | 22 ++++--- .../com/tencent/renderer/NativeRender.java | 4 +- .../renderer/NativeRenderProvider.java | 19 +----- .../com/tencent/renderer/NativeRenderer.java | 20 ++++-- .../com/tencent/renderer/RenderProxy.java | 7 ++ .../renderer/component/text/FontLoader.java | 66 ++++++++++++------- .../renderer/node/TextVirtualNode.java | 15 +++-- 14 files changed, 117 insertions(+), 117 deletions(-) diff --git a/framework/android/connector/renderer/native/src/main/java/com/openhippy/connector/NativeRenderer.java b/framework/android/connector/renderer/native/src/main/java/com/openhippy/connector/NativeRenderer.java index 4ed01ea69f5..f47b419af46 100644 --- a/framework/android/connector/renderer/native/src/main/java/com/openhippy/connector/NativeRenderer.java +++ b/framework/android/connector/renderer/native/src/main/java/com/openhippy/connector/NativeRenderer.java @@ -22,6 +22,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.tencent.mtt.hippy.common.Callback; +import com.tencent.mtt.hippy.modules.Promise; import com.tencent.renderer.FrameworkProxy; import com.tencent.renderer.RenderProxy; import java.util.List; @@ -143,6 +144,17 @@ public void destroy() { mRenderer = null; } + @Override + public void loadFontAndRefreshWindow(@NonNull String fontFamily, @NonNull String fontUrl, + int rootId, Object promise) { + if (!(promise instanceof Promise)) { + promise = null; + } + if (mRenderer != null) { + mRenderer.loadFontAndRefreshWindow(fontFamily, fontUrl, rootId, (Promise) promise); + } + } + @Override public int getInstanceId() { return mInstanceId; diff --git a/framework/android/connector/support/src/main/java/com/openhippy/connector/RenderConnector.java b/framework/android/connector/support/src/main/java/com/openhippy/connector/RenderConnector.java index d3ffc92db65..8933044dbff 100644 --- a/framework/android/connector/support/src/main/java/com/openhippy/connector/RenderConnector.java +++ b/framework/android/connector/support/src/main/java/com/openhippy/connector/RenderConnector.java @@ -19,8 +19,10 @@ import android.content.Context; import android.view.View; import android.view.ViewGroup; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; + import java.util.List; import java.util.Map; @@ -33,6 +35,8 @@ public interface RenderConnector extends Connector { void onRuntimeInitialized(int rootId); + void loadFontAndRefreshWindow(@NonNull String fontFamily, @NonNull String fontUrl, int rootId, Object promise); + void recordSnapshot(int rootId, @NonNull Object callback); View replaySnapshot(@NonNull Context context, @NonNull byte[] buffer); diff --git a/framework/android/src/main/java/com/tencent/mtt/hippy/HippyEngineContext.java b/framework/android/src/main/java/com/tencent/mtt/hippy/HippyEngineContext.java index 17b7f195c39..44e00dbd68e 100644 --- a/framework/android/src/main/java/com/tencent/mtt/hippy/HippyEngineContext.java +++ b/framework/android/src/main/java/com/tencent/mtt/hippy/HippyEngineContext.java @@ -22,6 +22,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.openhippy.connector.JsDriver; +import com.openhippy.connector.RenderConnector; import com.tencent.devtools.DevtoolsManager; import com.tencent.mtt.hippy.HippyEngine.ModuleLoadStatus; import com.tencent.mtt.hippy.bridge.HippyBridgeManager; @@ -65,6 +66,8 @@ public interface HippyEngineContext extends BaseEngineContext { ThreadExecutor getThreadExecutor(); + RenderConnector getRenderer(); + ViewGroup getRootView(); View getRootView(int rootId); diff --git a/framework/android/src/main/java/com/tencent/mtt/hippy/HippyEngineManagerImpl.java b/framework/android/src/main/java/com/tencent/mtt/hippy/HippyEngineManagerImpl.java index 769a3cff1b1..7ad440c80e9 100644 --- a/framework/android/src/main/java/com/tencent/mtt/hippy/HippyEngineManagerImpl.java +++ b/framework/android/src/main/java/com/tencent/mtt/hippy/HippyEngineManagerImpl.java @@ -940,7 +940,7 @@ DomManager getDomManager() { } @NonNull - RenderConnector getRenderer() { + public RenderConnector getRenderer() { return mRenderer; } 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 index e1755369f2e..e0d1aee07af 100644 --- 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 @@ -16,31 +16,26 @@ package com.tencent.mtt.hippy.modules.nativemodules.font; +import com.openhippy.connector.RenderConnector; 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); - mNativeRender = NativeRendererManager.getNativeRenderer(context.getRootView().getContext()); - mFontLoader = mNativeRender.getFontLoader(); rootId = context.getRootView().getId(); } @HippyMethod(name = "load") public void load(final String fontFamily, final String fontUrl, final Promise promise) { - mFontLoader.loadAndRefresh(fontFamily, fontUrl, mNativeRender, rootId, promise); + RenderConnector renderer = mContext.getRenderer(); + renderer.loadFontAndRefreshWindow(fontFamily, fontUrl, rootId, promise); } } 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 d920f128cc0..205c48bc1b5 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,9 +41,8 @@ jobject GetNativeRendererInstance(JNIEnv* j_env, jobject j_object, jint j_render_manager_id); -void MarkTextNodeDirty(JNIEnv *j_env, jobject j_object, jint j_render_manager_id, jint j_root_id); -void RefreshWindow(JNIEnv *j_env, jobject j_object, jint j_render_manager_id, jint j_root_id); +void RefreshTextWindow(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 44cb4bade6b..9120761f005 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 @@ -78,14 +78,9 @@ REGISTER_JNI("com/tencent/renderer/NativeRenderProvider", UpdateRootSize) REGISTER_JNI("com/tencent/renderer/NativeRenderProvider", - "refreshWindow", + "refreshTextWindow", "(II)V", - RefreshWindow) - -REGISTER_JNI("com/tencent/renderer/NativeRenderProvider", - "markTextNodeDirty", - "(II)V", - MarkTextNodeDirty) + RefreshTextWindow) static jint JNI_OnLoad(__unused JavaVM* j_vm, __unused void* reserved) { auto j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); @@ -173,45 +168,17 @@ void MarkTextNodeDirtyRecursive(const std::shared_ptr& node) { } } -void MarkTextNodeDirty(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) << "MarkTextNodeDirty j_render_manager_id invalid"; - return; - } - std::shared_ptr dom_manager = render_manager->GetDomManager(); - if (dom_manager == nullptr) { - FOOTSTONE_DLOG(WARNING) << "MarkTextNodeDirty 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) << "MarkTextNodeDirty root_node is nullptr"; - return; - } - - std::vector> ops = {[root_node] { - MarkTextNodeDirtyRecursive(root_node); - }}; - dom_manager->PostTask(Scene(std::move(ops))); -} - -void RefreshWindow(JNIEnv *j_env, jobject j_object, jint j_render_manager_id, jint j_root_id) { +void RefreshTextWindow(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) << "RefreshWindow j_render_manager_id invalid"; + FOOTSTONE_DLOG(WARNING) << "RefreshTextWindow j_render_manager_id invalid"; return; } std::shared_ptr dom_manager = render_manager->GetDomManager(); if (dom_manager == nullptr) { - FOOTSTONE_DLOG(WARNING) << "RefreshWindow dom_manager is nullptr"; + FOOTSTONE_DLOG(WARNING) << "RefreshTextWindow dom_manager is nullptr"; return; } auto& root_map = RootNode::PersistentMap(); @@ -219,12 +186,13 @@ void RefreshWindow(JNIEnv *j_env, jobject j_object, jint j_render_manager_id, ji 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) << "RefreshWindow root_node is nullptr"; + FOOTSTONE_DLOG(WARNING) << "RefreshTextWindow root_node is nullptr"; return; } std::vector> ops; ops.emplace_back([dom_manager, root_node]{ + MarkTextNodeDirtyRecursive(root_node); dom_manager->DoLayout(root_node); dom_manager->EndBatch(root_node); }); 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 380d56913b8..d99f7348b9b 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 @@ -100,7 +100,6 @@ public class HippyTextInput extends AppCompatEditText implements HippyViewBase, private String mFontFamily; private String mFontUrl; private Paint mTextPaint; - protected FontLoader mFontLoader; protected boolean mFromFontLoader = false; public HippyTextInput(Context context) { @@ -108,11 +107,6 @@ public HippyTextInput(Context context) { setFocusable(true); setFocusableInTouchMode(true); setOverScrollMode(View.OVER_SCROLL_IF_CONTENT_SCROLLS); - - NativeRender nativeRenderer = NativeRendererManager.getNativeRenderer(context); - if (nativeRenderer != null) { - mFontLoader = nativeRenderer.getFontLoader(); - } mDefaultGravityHorizontal = getGravity() & (Gravity.HORIZONTAL_GRAVITY_MASK | Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK); @@ -215,7 +209,12 @@ public void setTextLineHeight(int lineHeight) { } public void onBatchComplete() { - if (!mFromFontLoader && mFontLoader != null && mFontLoader.isFontLoaded(mFontFamily)) { + NativeRender nativeRenderer = NativeRendererManager.getNativeRenderer(this.getContext()); + FontLoader fontLoader = null; + if (nativeRenderer != null) { + fontLoader = nativeRenderer.getFontLoader(); + } + if (!mFromFontLoader && fontLoader != null && fontLoader.isFontLoaded(mFontFamily)) { mShouldUpdateTypeface = true; mFromFontLoader = true; } @@ -772,7 +771,12 @@ public void setFontFamily(String family) { if (!Objects.equals(mFontFamily, family)) { mFontFamily = family; mShouldUpdateTypeface = true; - if (mFromFontLoader && mFontLoader != null && !mFontLoader.isFontLoaded(mFontFamily)) { + NativeRender nativeRenderer = NativeRendererManager.getNativeRenderer(this.getContext()); + FontLoader fontLoader = null; + if (nativeRenderer != null) { + fontLoader = nativeRenderer.getFontLoader(); + } + if (mFromFontLoader && fontLoader != null && !fontLoader.isFontLoaded(mFontFamily)) { mFromFontLoader = false; } } @@ -803,7 +807,7 @@ private void updateTypeface() { FontLoader loader = nativeRenderer == null ? null : nativeRenderer.getFontLoader(); if (loader != null) { int rootId = nativeRenderer.getRootView(this).getId(); - loader.loadIfNeeded(mFontFamily, mFontUrl, nativeRenderer, rootId); + loader.loadIfNeeded(mFontFamily, mFontUrl, rootId); } } FontAdapter fontAdapter = nativeRenderer == null ? null : nativeRenderer.getFontAdapter(); 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 b902f7bfe80..bd83d2df238 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 @@ -108,9 +108,7 @@ 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 refreshWindow(int rootId); + void refreshTextWindow(int rootId); void updateDimension(int width, int height); 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 fc2b89cc074..edf80284256 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,12 +337,8 @@ public void onSizeChanged(int rootId, int width, int height) { updateRootSize(mInstanceId, rootId, PixelUtil.px2dp(width), PixelUtil.px2dp(height)); } - public void refreshWindow(int rootId) { - refreshWindow(mInstanceId, rootId); - } - - public void markTextNodeDirty(int rootId) { - markTextNodeDirty(mInstanceId, rootId); + public void refreshTextWindow(int rootId) { + refreshTextWindow(mInstanceId, rootId); } public void onSizeChanged(int rootId, int nodeId, int width, int height, boolean isSync) { @@ -449,16 +445,7 @@ private void dispatchEventImpl(int rootId, int nodeId, @NonNull String eventName * @param instanceId the unique id of native (C++) render manager */ @SuppressWarnings("JavaJniMissingFunction") - private native void refreshWindow(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 instanceId, int rootId); + private native void refreshTextWindow(int instanceId, int rootId); /** * Updates the size to the specified node, such as modal node, should set new window size before 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 5e285c1843d..efc18bb1fef 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 @@ -40,6 +40,7 @@ import com.tencent.mtt.hippy.common.BaseEngineContext; import com.tencent.mtt.hippy.common.Callback; import com.tencent.mtt.hippy.common.LogAdapter; +import com.tencent.mtt.hippy.modules.Promise; import com.tencent.mtt.hippy.serialization.nio.reader.BinaryReader; import com.tencent.mtt.hippy.serialization.nio.reader.SafeHeapReader; import com.tencent.mtt.hippy.serialization.nio.writer.SafeHeapWriter; @@ -227,7 +228,7 @@ public ImageLoaderAdapter getImageLoader() { @Nullable public FontLoader getFontLoader() { if (mFontLoader == null && getVfsManager() != null) { - mFontLoader = new FontLoader(getVfsManager()); + mFontLoader = new FontLoader(getVfsManager(), this); } return mFontLoader; } @@ -415,12 +416,8 @@ private void onSizeChanged(int rootId, int w, int h) { mRenderProvider.onSizeChanged(rootId, w, h); } - public void markTextNodeDirty(int rootId) { - mRenderProvider.markTextNodeDirty(rootId); - } - - public void refreshWindow(int rootId) { - mRenderProvider.refreshWindow(rootId); + public void refreshTextWindow(int rootId) { + mRenderProvider.refreshTextWindow(rootId); } @Override @@ -1144,6 +1141,15 @@ private boolean collectNodeInfo(@NonNull RenderNode child, int pid, int outerLef return true; } + @Override + public void loadFontAndRefreshWindow(@NonNull String fontFamily, @NonNull String fontUrl, + int rootId, final Promise promise) { + if (mFontLoader == null && getVfsManager() != null) { + mFontLoader = new FontLoader(getVfsManager(), this); + } + mFontLoader.loadAndRefresh(fontFamily, fontUrl, rootId, promise); + } + private interface UITaskExecutor { void exec(); diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/RenderProxy.java b/renderer/native/android/src/main/java/com/tencent/renderer/RenderProxy.java index 1aa50a9f303..d89551bbb68 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/RenderProxy.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/RenderProxy.java @@ -23,6 +23,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.tencent.mtt.hippy.common.Callback; +import com.tencent.mtt.hippy.modules.Promise; + import java.util.List; import java.util.Map; @@ -131,4 +133,9 @@ public interface RenderProxy { */ void removeSnapshotView(); + /** + * Notify renderer to load font from url and refresh text window. + */ + void loadFontAndRefreshWindow(@NonNull String fontFamily, @NonNull String fontUrl, int rootId, final Promise promise); + } 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 index c0bb3cc8498..a6898ac40dd 100644 --- 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 @@ -34,27 +34,35 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; public class FontLoader { - private final VfsManager mVfsManager; + private final WeakReference mNativeRenderRef; + private final WeakReference mVfsManager; private final File mFontDir; private final File mFontUrlDictPath; - private HashMap mFontUrlDict; - private HashSet mFontsLoaded; + private Map mConcurrentFontUrlDict; + private final Set mConcurrentFontsLoaded; + private final Set mConcurrentUrlsLoading; private static final String[] ALLOWED_EXTENSIONS = {"otf", "ttf"}; private static final String FONT_DIR_NAME = "HippyFonts"; private static final String FONT_URL_DICT_NAME = "fontUrlDict.ser"; - public FontLoader(VfsManager vfsManager) { - mVfsManager = vfsManager; + public FontLoader(VfsManager vfsManager, NativeRender nativeRender) { + mNativeRenderRef = new WeakReference<>(nativeRender); + mVfsManager = new WeakReference<>(vfsManager); mFontDir = new File(ContextHolder.getAppContext().getCacheDir(), FONT_DIR_NAME); mFontUrlDictPath = new File(mFontDir, FONT_URL_DICT_NAME); - mFontsLoaded = new HashSet<>(); + mConcurrentFontsLoaded = new CopyOnWriteArraySet<>(); + mConcurrentUrlsLoading = new CopyOnWriteArraySet<>(); } private boolean saveFontFile(byte[] byteArray, String fileName, Promise promise) { @@ -81,14 +89,14 @@ private boolean saveFontFile(byte[] byteArray, String fileName, Promise promise) } } - public boolean isFontLoaded(String fontFamily) { - return mFontsLoaded.contains(fontFamily); + public boolean isFontLoaded(String fontFamily) { + return mConcurrentFontsLoaded.contains(fontFamily); } private void saveFontUrlDictFile() { try (FileOutputStream fos = new FileOutputStream(mFontUrlDictPath); ObjectOutputStream oos = new ObjectOutputStream(fos)) { - oos.writeObject(mFontUrlDict); + oos.writeObject(mConcurrentFontUrlDict); LogUtils.d("FontLoader", "Save fontUrlDict.ser success"); } catch (IOException e) { LogUtils.d("FontLoader", "Save fontUrlDict.ser failed"); @@ -106,38 +114,43 @@ public static String getFileExtension(String url) { return ""; } - public boolean loadIfNeeded(final String fontFamily, final String fontUrl, NativeRender render, - int rootId) { - if (mFontUrlDict == null) { + public boolean loadIfNeeded(final String fontFamily, final String fontUrl, int rootId) { + if (mConcurrentFontUrlDict == null) { + Map fontUrlDict; try (FileInputStream fis = new FileInputStream(mFontUrlDictPath); ObjectInputStream ois = new ObjectInputStream(fis)) { - mFontUrlDict = (HashMap) ois.readObject(); + fontUrlDict = (Map) ois.readObject(); } catch (IOException | ClassNotFoundException e) { - mFontUrlDict = new HashMap<>(); + fontUrlDict = new HashMap<>(); } + mConcurrentFontUrlDict = new ConcurrentHashMap<>(fontUrlDict); } - String fontFileName = mFontUrlDict.get(fontUrl); + String fontFileName = mConcurrentFontUrlDict.get(fontUrl); if (fontFileName != null) { File fontFile = new File(mFontDir, fontFileName); if (fontFile.exists()) { return false; } } - loadAndRefresh(fontFamily, fontUrl, render, rootId, null); + if (mConcurrentUrlsLoading.contains(fontUrl)) { + return false; + } + loadAndRefresh(fontFamily, fontUrl, rootId, null); return true; } - public void loadAndRefresh(final String fontFamily, final String fontUrl, NativeRender render, - int rootId, Promise promise) { - LogUtils.d("FontLoader", "Start load" + fontUrl); + public void loadAndRefresh(final String fontFamily, final String fontUrl, 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, + mConcurrentUrlsLoading.add(fontUrl); + mVfsManager.get().fetchResourceAsync(fontUrl, null, null, new FetchResourceCallback() { @Override public void onFetchCompleted(@NonNull final ResourceDataHolder dataHolder) { @@ -152,14 +165,17 @@ public void onFetchCompleted(@NonNull final ResourceDataHolder dataHolder) { String fileName = fontFamily + getFileExtension(fontUrl); if (saveFontFile(bytes, fileName, promise)) { LogUtils.d("FontLoader", "Fetch font file success"); - mFontsLoaded.add(fontFamily); - mFontUrlDict.put(fontUrl, fileName); + mConcurrentFontsLoaded.add(fontFamily); + mConcurrentFontUrlDict.put(fontUrl, fileName); saveFontUrlDictFile(); TypeFaceUtil.clearFontCache(fontFamily); - render.markTextNodeDirty(rootId); - render.refreshWindow(rootId); + NativeRender nativeRender = mNativeRenderRef.get(); + if (nativeRender != null) { + nativeRender.refreshTextWindow(rootId); + } } } + mConcurrentUrlsLoading.remove(fontUrl); dataHolder.recycle(); } 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 5853640e5dc..e9babd910f9 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 @@ -57,6 +57,8 @@ import com.tencent.renderer.component.text.TextVerticalAlignSpan; import com.tencent.renderer.component.text.TypeFaceUtil; import com.tencent.renderer.utils.FlexUtils.FlexMeasureMode; + +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -114,7 +116,7 @@ public class TextVirtualNode extends VirtualNode { @Nullable protected String mFontUrl; @Nullable - protected FontLoader mFontLoader; + protected WeakReference mFontLoaderRef; protected boolean mFromFontLoader = false; @Nullable protected SpannableStringBuilder mSpanned; @@ -131,14 +133,12 @@ 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(); + mFontLoaderRef = new WeakReference<>(nativeRender.getFontLoader()); if (I18nUtil.isRTL()) { mAlignment = Layout.Alignment.ALIGN_OPPOSITE; } @@ -488,8 +488,8 @@ 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()); + if (mFontUrl != null && !mFontUrl.isEmpty() && mFontLoaderRef.get() != null) { + mFontLoaderRef.get().loadIfNeeded(mFontFamily, mFontUrl, getRootId()); } ops.add(new SpanOperation(start, end, new AbsoluteSizeSpan(size))); ops.add(new SpanOperation(start, end, new TextStyleSpan(mItalic, mFontWeight, mFontFamily, mFontAdapter))); @@ -557,7 +557,8 @@ protected Layout createLayout() { @NonNull protected Layout createLayout(final float width, final FlexMeasureMode widthMode) { - if (!mFromFontLoader && mFontLoader != null && mFontLoader.isFontLoaded(mFontFamily)) { + FontLoader fontLoader = mFontLoaderRef.get(); + if (!mFromFontLoader && fontLoader != null && fontLoader.isFontLoaded(mFontFamily)) { mDirty = true; mFromFontLoader = true; } From 5490c79c479cfeddca796993b3f7b328076fd41c Mon Sep 17 00:00:00 2001 From: arnonchen <745735906@qq.com> Date: Tue, 22 Oct 2024 11:22:29 +0800 Subject: [PATCH 07/20] fix(android, react, vue): add font load state, change resolve from void to string --- .../src/modules/FontLoader/index.jsx | 4 +- .../native-demos/demo-vue-native.vue | 3 +- .../native-demo/demo-vue-native.vue | 3 +- .../src/runtime/native/index.ts | 4 +- .../native-modules/font-loader-module.ts | 2 +- .../packages/hippy-vue/src/runtime/native.ts | 2 +- .../renderer/component/text/FontLoader.java | 65 ++++++++++--------- 7 files changed, 43 insertions(+), 40 deletions(-) diff --git a/driver/js/examples/hippy-react-demo/src/modules/FontLoader/index.jsx b/driver/js/examples/hippy-react-demo/src/modules/FontLoader/index.jsx index 4828686db3a..f204779e6f7 100644 --- a/driver/js/examples/hippy-react-demo/src/modules/FontLoader/index.jsx +++ b/driver/js/examples/hippy-react-demo/src/modules/FontLoader/index.jsx @@ -77,8 +77,8 @@ export default class LoadFontExample extends React.Component { async loadFont() { this.setState({ fontFamily: this.state.inputFontFamily }); await FontLoaderModule.load(this.state.fontFamily, this.state.fontUrl) - .then(() => { - this.setState({ loadState: 'Success' }); + .then((result) => { + this.setState({ loadState: result }); }) .catch((error) => { this.setState({ loadState: error }); diff --git a/driver/js/examples/hippy-vue-demo/src/components/native-demos/demo-vue-native.vue b/driver/js/examples/hippy-vue-demo/src/components/native-demos/demo-vue-native.vue index aa1c99eb72d..622a555e6f2 100644 --- a/driver/js/examples/hippy-vue-demo/src/components/native-demos/demo-vue-native.vue +++ b/driver/js/examples/hippy-vue-demo/src/components/native-demos/demo-vue-native.vue @@ -454,8 +454,7 @@ export default { console.log('fontUrl:', this.fontUrl) let result; try { - await Vue.Native.FontLoader.load(this.fontFamily, this.fontUrl); - result = 'success'; + result = await Vue.Native.FontLoader.load(this.fontFamily, this.fontUrl); } catch (error) { result = error.message; } diff --git a/driver/js/examples/hippy-vue-next-demo/src/components/native-demo/demo-vue-native.vue b/driver/js/examples/hippy-vue-next-demo/src/components/native-demo/demo-vue-native.vue index e1fad9a0ab4..f62d7ec52f9 100644 --- a/driver/js/examples/hippy-vue-next-demo/src/components/native-demo/demo-vue-native.vue +++ b/driver/js/examples/hippy-vue-next-demo/src/components/native-demo/demo-vue-native.vue @@ -429,8 +429,7 @@ export default defineComponent({ console.log('load fontUrl:', fontUrl.value) let result; try { - await Native.FontLoader.load(fontFamily.value, fontUrl.value); - result = 'success'; + result = await Native.FontLoader.load(fontFamily.value, fontUrl.value); } catch (error) { result = error.message; } 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 c6eaba00f19..14a910bf969 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 @@ -242,7 +242,7 @@ export interface NativeApiType { }; FontLoader: { - load: (fontFamily: string, url: string) => Promise; + load: (fontFamily: string, url: string) => Promise; }; // include window and screen info @@ -528,7 +528,7 @@ export const Native: NativeApiType = { * @param fontFamily * @param url - font url */ - load(fontFamily, url): Promise { + load(fontFamily: string, url: string): Promise { return Native.callNativeWithPromise.call( this, 'FontLoaderModule', 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 index 16de6f1d1a3..73cbbd664e3 100644 --- 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 @@ -19,5 +19,5 @@ */ export interface FontLoaderModule { - load: (fontFamily: string, url: string) => undefined; + load: (fontFamily: string, url: string) => string; } diff --git a/driver/js/packages/hippy-vue/src/runtime/native.ts b/driver/js/packages/hippy-vue/src/runtime/native.ts index ab61d08a463..8d1966a8622 100644 --- a/driver/js/packages/hippy-vue/src/runtime/native.ts +++ b/driver/js/packages/hippy-vue/src/runtime/native.ts @@ -453,7 +453,7 @@ const Native: NeedToTyped = { * @param {string} fontFamily - The font family to download, * @param {string} url - The url where to download the font. */ - load(fontFamily: NeedToTyped, url: NeedToTyped) { + load(fontFamily: string, url: string) { return callNativeWithPromise.call(this, 'FontLoaderModule', 'load', fontFamily, url); }, }, 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 index a6898ac40dd..b830fdd0e3a 100644 --- 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 @@ -38,31 +38,32 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArraySet; public class FontLoader { private final WeakReference mNativeRenderRef; private final WeakReference mVfsManager; private final File mFontDir; - private final File mFontUrlDictPath; - private Map mConcurrentFontUrlDict; - private final Set mConcurrentFontsLoaded; - private final Set mConcurrentUrlsLoading; + private final File mFontUrlMapPath; + private Map mConcurrentFontUrlMap; + private final Map mConcurrentFontLoadStateMap; private static final String[] ALLOWED_EXTENSIONS = {"otf", "ttf"}; private static final String FONT_DIR_NAME = "HippyFonts"; - private static final String FONT_URL_DICT_NAME = "fontUrlDict.ser"; + private static final String FONT_URL_MAP_NAME = "fontUrlMap.ser"; + + public enum FontLoadState { + FONT_LOADING, + FONT_LOADED + } public FontLoader(VfsManager vfsManager, NativeRender nativeRender) { mNativeRenderRef = new WeakReference<>(nativeRender); mVfsManager = new WeakReference<>(vfsManager); mFontDir = new File(ContextHolder.getAppContext().getCacheDir(), FONT_DIR_NAME); - mFontUrlDictPath = new File(mFontDir, FONT_URL_DICT_NAME); - mConcurrentFontsLoaded = new CopyOnWriteArraySet<>(); - mConcurrentUrlsLoading = new CopyOnWriteArraySet<>(); + mFontUrlMapPath = new File(mFontDir, FONT_URL_MAP_NAME); + mConcurrentFontLoadStateMap = new ConcurrentHashMap<>(); } private boolean saveFontFile(byte[] byteArray, String fileName, Promise promise) { @@ -78,7 +79,7 @@ private boolean saveFontFile(byte[] byteArray, String fileName, Promise promise) try (FileOutputStream fos = new FileOutputStream(fontFile)) { fos.write(byteArray); if (promise != null) { - promise.resolve(null); + promise.resolve(fileName + " download success!"); } return true; } catch (IOException e) { @@ -90,16 +91,17 @@ private boolean saveFontFile(byte[] byteArray, String fileName, Promise promise) } public boolean isFontLoaded(String fontFamily) { - return mConcurrentFontsLoaded.contains(fontFamily); + return fontFamily != null && + mConcurrentFontLoadStateMap.get(fontFamily) == FontLoadState.FONT_LOADED; } - private void saveFontUrlDictFile() { - try (FileOutputStream fos = new FileOutputStream(mFontUrlDictPath); + private void saveFontUrlMapFile() { + try (FileOutputStream fos = new FileOutputStream(mFontUrlMapPath); ObjectOutputStream oos = new ObjectOutputStream(fos)) { - oos.writeObject(mConcurrentFontUrlDict); - LogUtils.d("FontLoader", "Save fontUrlDict.ser success"); + oos.writeObject(mConcurrentFontUrlMap); + LogUtils.d("FontLoader", "Save fontUrlMap.ser success"); } catch (IOException e) { - LogUtils.d("FontLoader", "Save fontUrlDict.ser failed"); + LogUtils.d("FontLoader", "Save fontUrlMap.ser failed"); } } @@ -115,24 +117,24 @@ public static String getFileExtension(String url) { } public boolean loadIfNeeded(final String fontFamily, final String fontUrl, int rootId) { - if (mConcurrentFontUrlDict == null) { - Map fontUrlDict; - try (FileInputStream fis = new FileInputStream(mFontUrlDictPath); + if (mConcurrentFontUrlMap == null) { + Map fontUrlMap; + try (FileInputStream fis = new FileInputStream(mFontUrlMapPath); ObjectInputStream ois = new ObjectInputStream(fis)) { - fontUrlDict = (Map) ois.readObject(); + fontUrlMap = (Map) ois.readObject(); } catch (IOException | ClassNotFoundException e) { - fontUrlDict = new HashMap<>(); + fontUrlMap = new HashMap<>(); } - mConcurrentFontUrlDict = new ConcurrentHashMap<>(fontUrlDict); + mConcurrentFontUrlMap = new ConcurrentHashMap<>(fontUrlMap); } - String fontFileName = mConcurrentFontUrlDict.get(fontUrl); + String fontFileName = mConcurrentFontUrlMap.get(fontUrl); if (fontFileName != null) { File fontFile = new File(mFontDir, fontFileName); if (fontFile.exists()) { return false; } } - if (mConcurrentUrlsLoading.contains(fontUrl)) { + if (mConcurrentFontLoadStateMap.containsKey(fontFamily)) { return false; } loadAndRefresh(fontFamily, fontUrl, rootId, null); @@ -149,7 +151,7 @@ public void loadAndRefresh(final String fontFamily, final String fontUrl, int ro } return; } - mConcurrentUrlsLoading.add(fontUrl); + mConcurrentFontLoadStateMap.put(fontFamily, FontLoadState.FONT_LOADING); mVfsManager.get().fetchResourceAsync(fontUrl, null, null, new FetchResourceCallback() { @Override @@ -158,6 +160,7 @@ public void onFetchCompleted(@NonNull final ResourceDataHolder dataHolder) { if (dataHolder.resultCode != ResourceDataHolder.RESOURCE_LOAD_SUCCESS_CODE || bytes == null || bytes.length <= 0) { + mConcurrentFontLoadStateMap.remove(fontFamily); if (promise != null) { promise.reject("Fetch font file failed, url=" + fontUrl); } @@ -165,17 +168,19 @@ public void onFetchCompleted(@NonNull final ResourceDataHolder dataHolder) { String fileName = fontFamily + getFileExtension(fontUrl); if (saveFontFile(bytes, fileName, promise)) { LogUtils.d("FontLoader", "Fetch font file success"); - mConcurrentFontsLoaded.add(fontFamily); - mConcurrentFontUrlDict.put(fontUrl, fileName); - saveFontUrlDictFile(); + mConcurrentFontLoadStateMap.put(fontFamily, FontLoadState.FONT_LOADED); + mConcurrentFontUrlMap.put(fontUrl, fileName); + saveFontUrlMapFile(); TypeFaceUtil.clearFontCache(fontFamily); NativeRender nativeRender = mNativeRenderRef.get(); if (nativeRender != null) { nativeRender.refreshTextWindow(rootId); } } + else { + mConcurrentFontLoadStateMap.remove(fontFamily); + } } - mConcurrentUrlsLoading.remove(fontUrl); dataHolder.recycle(); } From 76546e98ba2e42c79d9ee2249758735598bf816a Mon Sep 17 00:00:00 2001 From: arnonchen <745735906@qq.com> Date: Tue, 22 Oct 2024 20:00:54 +0800 Subject: [PATCH 08/20] fix(ios): remove IO operations from main thread and ensure thread safety --- .../module/fontLoader/HippyFontLoaderModule.h | 21 +++++++ .../fontLoader/HippyFontLoaderModule.mm | 55 ++++++++++++++++--- renderer/native/ios/renderer/HippyFont.mm | 20 ++++--- 3 files changed, 79 insertions(+), 17 deletions(-) diff --git a/framework/ios/module/fontLoader/HippyFontLoaderModule.h b/framework/ios/module/fontLoader/HippyFontLoaderModule.h index 1877330655d..caf1450d957 100644 --- a/framework/ios/module/fontLoader/HippyFontLoaderModule.h +++ b/framework/ios/module/fontLoader/HippyFontLoaderModule.h @@ -27,6 +27,12 @@ NS_ASSUME_NONNULL_BEGIN HIPPY_EXTERN NSString *const HippyLoadFontNotification; +typedef NS_ENUM(NSInteger, HippyFontUrlState) { + HippyFontUrlLoading = 0, + HippyFontUrlLoaded = 1, + HippyFontUrlFailed = 2, +}; + @interface HippyFontLoaderModule : NSObject /** @@ -44,6 +50,21 @@ HIPPY_EXTERN NSString *const HippyLoadFontNotification; */ + (void)registerFontIfNeeded:(NSString *)fontFamily; +/** + * Whether the font is downloading from the url. + * + * @param url - The font url needs to download from. + * @return Yes if the font is downloading from the url. + */ ++ (BOOL) isUrlLoading:(NSString *)url; + +/** + * Get the serial queue in HippyFontLoaderModule for asyn serial operations. + * + * @return The serial dispatch_queue_t. + */ ++ (dispatch_queue_t) getFontSerialQueue; + @end NS_ASSUME_NONNULL_END diff --git a/framework/ios/module/fontLoader/HippyFontLoaderModule.mm b/framework/ios/module/fontLoader/HippyFontLoaderModule.mm index 0d5016721d7..822a4b954e7 100644 --- a/framework/ios/module/fontLoader/HippyFontLoaderModule.mm +++ b/framework/ios/module/fontLoader/HippyFontLoaderModule.mm @@ -36,16 +36,20 @@ static NSUInteger const FontLoaderErrorDirectoryError = 2; static NSUInteger const FontLoaderErrorRequestError = 3; static NSUInteger const FontLoaderErrorRegisterError = 4; +static NSUInteger const FontLoaderErrorWriteFileError = 4; NSString *const HippyFontDirName = @"HippyFonts"; NSString *const HippyFontUrlCacheName = @"urlToFile.plist"; NSString *const HippyFontFamilyCacheName = @"fontFaimilyToFiles.plist"; +static dispatch_queue_t serialQueue; static NSMutableDictionary *urlToFile; static NSMutableDictionary *fontFamilyToFiles; +static NSMutableDictionary *urlLoadState; +static NSMutableArray *fontRegistered = [NSMutableArray array]; static NSString *fontDirPath; static NSString *fontUrlCachePath; static NSString *fontFamilyCachePath; -static NSMutableArray *fontRegistered = [NSMutableArray array]; + @implementation HippyFontLoaderModule @@ -65,11 +69,24 @@ - (instancetype)init { fontDirPath = [cachesDirectory stringByAppendingPathComponent:HippyFontDirName]; fontUrlCachePath = [fontDirPath stringByAppendingPathComponent:HippyFontUrlCacheName]; fontFamilyCachePath = [fontDirPath stringByAppendingPathComponent:HippyFontFamilyCacheName]; + serialQueue = dispatch_queue_create("com.tencent.hippy.FontLoaderQueue", DISPATCH_QUEUE_SERIAL); }); } return self; } ++ (dispatch_queue_t) getFontSerialQueue { + return serialQueue; +} + ++ (void) setUrl:(NSString *)url state:(HippyFontUrlState)state { + [urlLoadState setObject:@(state) forKey:url]; +} + ++ (BOOL) isUrlLoading:(NSString *)url { + return [[urlLoadState objectForKey:url] integerValue] == HippyFontUrlLoading; +} + + (void) initDictIfNeeded { if (fontFamilyToFiles == nil) { fontFamilyToFiles = [NSMutableDictionary dictionaryWithContentsOfFile:fontFamilyCachePath]; @@ -182,8 +199,16 @@ - (void)cacheFontfamily:(NSString *)fontFamily url:(NSString *)url fileName:(NSS } return; } - NSFileManager *fileManager = [NSFileManager defaultManager]; + @synchronized (self) { + if ([HippyFontLoaderModule isUrlLoading:urlString]) { + resolve([NSString stringWithFormat:@"url \"%@\" is downloading!", urlString]); + return; + } + [HippyFontLoaderModule setUrl:urlString state:HippyFontUrlLoading]; + } + + NSFileManager *fileManager = [NSFileManager defaultManager]; if (![fileManager fileExistsAtPath:fontDirPath]) { NSError *error; [fileManager createDirectoryAtPath:fontDirPath withIntermediateDirectories:YES attributes:nil error:&error]; @@ -192,6 +217,7 @@ - (void)cacheFontfamily:(NSString *)fontFamily url:(NSString *)url fileName:(NSS if (reject) { reject(errorKey, @"directory create error", error); } + [HippyFontLoaderModule setUrl:urlString state:HippyFontUrlFailed]; return; } } @@ -206,20 +232,31 @@ - (void)cacheFontfamily:(NSString *)fontFamily url:(NSString *)url fileName:(NSS completionHandler:^(NSData *data, NSDictionary *userInfo, NSURLResponse *response, NSError *error) { __strong __typeof(weakSelf) strongSelf = weakSelf; if (error) { - NSString *errorKey = [NSString stringWithFormat:@"%lu", FontLoaderErrorRequestError]; if (reject) { + NSString *errorKey = [NSString stringWithFormat:@"%lu", FontLoaderErrorRequestError]; reject(errorKey, @"font request error", error); } + [HippyFontLoaderModule setUrl:urlString state:HippyFontUrlFailed]; return; } NSString *fileName = [fontFamily stringByAppendingFormat:@".%@", [response.suggestedFilename pathExtension]]; NSString *fontFilePath = [fontDirPath stringByAppendingPathComponent:fileName]; - [data writeToFile:fontFilePath atomically:YES]; - [strongSelf cacheFontfamily:fontFamily url:urlString fileName:fileName]; - [[NSNotificationCenter defaultCenter] postNotificationName:HippyFontChangeTriggerNotification object:nil]; - if (resolve) { - HippyLogInfo(@"download font file \"%@\" success!", fileName); - resolve(nil); + if ([data writeToFile:fontFilePath atomically:YES]) { + dispatch_async([HippyFontLoaderModule getFontSerialQueue], ^{ + [strongSelf cacheFontfamily:fontFamily url:urlString fileName:fileName]; + [[NSNotificationCenter defaultCenter] postNotificationName:HippyFontChangeTriggerNotification object:nil]; + }); + if (resolve) { + resolve([NSString stringWithFormat:@"download font file \"%@\" success!", fileName]); + } + [HippyFontLoaderModule setUrl:urlString state:HippyFontUrlLoaded]; + } + else { + if (reject) { + NSString *errorKey = [NSString stringWithFormat:@"%lu", FontLoaderErrorWriteFileError]; + reject(errorKey, @"font request error", error); + } + [HippyFontLoaderModule setUrl:urlString state:HippyFontUrlFailed]; } }]; } diff --git a/renderer/native/ios/renderer/HippyFont.mm b/renderer/native/ios/renderer/HippyFont.mm index f242584a610..4b037678a40 100644 --- a/renderer/native/ios/renderer/HippyFont.mm +++ b/renderer/native/ios/renderer/HippyFont.mm @@ -206,12 +206,14 @@ + (UIFont *)updateFont:(UIFont *)font variant:(NSArray *)variant scaleMultiplier:(CGFloat)scaleMultiplier { // Defaults - if (url) { - NSString *fontPath = [HippyFontLoaderModule getFontPath:url]; - if (!fontPath && family) { - NSDictionary *userInfo = @{@"fontUrl": url, @"fontFamily": family}; - [[NSNotificationCenter defaultCenter] postNotificationName:HippyLoadFontNotification object:nil userInfo:userInfo]; - } + if (url && ![HippyFontLoaderModule isUrlLoading:url]) { + dispatch_async([HippyFontLoaderModule getFontSerialQueue], ^{ + NSString *fontPath = [HippyFontLoaderModule getFontPath:url]; + if (!fontPath && family) { + NSDictionary *userInfo = @{@"fontUrl": url, @"fontFamily": family}; + [[NSNotificationCenter defaultCenter] postNotificationName:HippyLoadFontNotification object:nil userInfo:userInfo]; + } + }); } // Defaults @@ -251,8 +253,10 @@ + (UIFont *)updateFont:(UIFont *)font BOOL didFindFont = NO; - if (fontNamesForFamilyName(familyName).count == 0) { - [HippyFontLoaderModule registerFontIfNeeded:familyName]; + if (fontNamesForFamilyName(familyName).count == 0) { + dispatch_async([HippyFontLoaderModule getFontSerialQueue], ^{ + [HippyFontLoaderModule registerFontIfNeeded:familyName]; + }); } // Handle system font as special case. This ensures that we preserve From 48e684f04b4bb889b5faf9566ec4c3dab9641476 Mon Sep 17 00:00:00 2001 From: arnonchen <745735906@qq.com> Date: Tue, 22 Oct 2024 20:36:03 +0800 Subject: [PATCH 09/20] fix(ios): fix the "given a font name rather than font family" condition --- renderer/native/ios/renderer/HippyFont.mm | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/renderer/native/ios/renderer/HippyFont.mm b/renderer/native/ios/renderer/HippyFont.mm index 4b037678a40..b5880732bcc 100644 --- a/renderer/native/ios/renderer/HippyFont.mm +++ b/renderer/native/ios/renderer/HippyFont.mm @@ -284,11 +284,16 @@ + (UIFont *)updateFont:(UIFont *)font // Gracefully handle being given a font name rather than font family, for // example: "Helvetica Light Oblique" rather than just "Helvetica". if (!didFindFont && familyName.length > 0 && fontNamesForFamilyName(familyName).count == 0) { - familyName = [HippyFont familyNameWithCSSNameMatching:familyName] ?: familyName; - fontWeight = weight ? fontWeight : weightOfFont(font); - isItalic = style ? isItalic : isItalicFont(font); - isCondensed = isCondensedFont(font); - font = cachedSystemFont(fontSize, fontWeight); + font = [UIFont fontWithName:familyName size:fontSize]; + if (font) { + didFindFont = YES; + } + else { + fontWeight = weight ? fontWeight : weightOfFont(font); + isItalic = style ? isItalic : isItalicFont(font); + isCondensed = isCondensedFont(font); + font = cachedSystemFont(fontSize, fontWeight); + } if (font) { // It's actually a font name, not a font family name, From e5624b57a82908762a1b40a65a721707ce474401 Mon Sep 17 00:00:00 2001 From: arnonchen <745735906@qq.com> Date: Thu, 24 Oct 2024 11:46:58 +0800 Subject: [PATCH 10/20] fead(ios, android): support load font from JSbundle --- .../module/fontLoader/HippyFontLoaderModule.h | 6 +- .../fontLoader/HippyFontLoaderModule.mm | 109 ++++++------ modules/vfs/ios/HippyFileHandler.mm | 11 +- modules/vfs/ios/HippyVFSDefines.h | 12 ++ modules/vfs/ios/VFSUriHandler.mm | 8 +- .../hippy/views/textinput/HippyTextInput.java | 10 +- .../renderer/component/text/FontLoader.java | 161 ++++++++++++++---- .../renderer/component/text/TypeFaceUtil.java | 33 +++- .../renderer/node/TextVirtualNode.java | 8 +- 9 files changed, 259 insertions(+), 99 deletions(-) diff --git a/framework/ios/module/fontLoader/HippyFontLoaderModule.h b/framework/ios/module/fontLoader/HippyFontLoaderModule.h index caf1450d957..8abad6de53b 100644 --- a/framework/ios/module/fontLoader/HippyFontLoaderModule.h +++ b/framework/ios/module/fontLoader/HippyFontLoaderModule.h @@ -28,9 +28,9 @@ NS_ASSUME_NONNULL_BEGIN HIPPY_EXTERN NSString *const HippyLoadFontNotification; typedef NS_ENUM(NSInteger, HippyFontUrlState) { - HippyFontUrlLoading = 0, - HippyFontUrlLoaded = 1, - HippyFontUrlFailed = 2, + HippyFontUrlLoading = 1, + HippyFontUrlLoaded = 2, + HippyFontUrlFailed = 3, }; @interface HippyFontLoaderModule : NSObject diff --git a/framework/ios/module/fontLoader/HippyFontLoaderModule.mm b/framework/ios/module/fontLoader/HippyFontLoaderModule.mm index 822a4b954e7..f77792166e2 100644 --- a/framework/ios/module/fontLoader/HippyFontLoaderModule.mm +++ b/framework/ios/module/fontLoader/HippyFontLoaderModule.mm @@ -27,6 +27,7 @@ #import "HippyBridge+VFSLoader.h" #import "HippyLog.h" #import "VFSUriLoader.h" +#import "HippyVFSDefines.h" #import "HippyUIManager.h" @@ -38,17 +39,17 @@ static NSUInteger const FontLoaderErrorRegisterError = 4; static NSUInteger const FontLoaderErrorWriteFileError = 4; NSString *const HippyFontDirName = @"HippyFonts"; -NSString *const HippyFontUrlCacheName = @"urlToFile.plist"; +NSString *const HippyFontUrlCacheName = @"urlToFilePath.plist"; NSString *const HippyFontFamilyCacheName = @"fontFaimilyToFiles.plist"; static dispatch_queue_t serialQueue; -static NSMutableDictionary *urlToFile; +static NSMutableDictionary *urlToFilePath; static NSMutableDictionary *fontFamilyToFiles; static NSMutableDictionary *urlLoadState; static NSMutableArray *fontRegistered = [NSMutableArray array]; static NSString *fontDirPath; -static NSString *fontUrlCachePath; -static NSString *fontFamilyCachePath; +static NSString *fontUrlSavePath; +static NSString *fontFamilySavePath; @implementation HippyFontLoaderModule @@ -67,8 +68,8 @@ - (instancetype)init { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); NSString *cachesDirectory = [paths objectAtIndex:0]; fontDirPath = [cachesDirectory stringByAppendingPathComponent:HippyFontDirName]; - fontUrlCachePath = [fontDirPath stringByAppendingPathComponent:HippyFontUrlCacheName]; - fontFamilyCachePath = [fontDirPath stringByAppendingPathComponent:HippyFontFamilyCacheName]; + fontUrlSavePath = [fontDirPath stringByAppendingPathComponent:HippyFontUrlCacheName]; + fontFamilySavePath = [fontDirPath stringByAppendingPathComponent:HippyFontFamilyCacheName]; serialQueue = dispatch_queue_create("com.tencent.hippy.FontLoaderQueue", DISPATCH_QUEUE_SERIAL); }); } @@ -89,15 +90,15 @@ + (BOOL) isUrlLoading:(NSString *)url { + (void) initDictIfNeeded { if (fontFamilyToFiles == nil) { - fontFamilyToFiles = [NSMutableDictionary dictionaryWithContentsOfFile:fontFamilyCachePath]; + fontFamilyToFiles = [NSMutableDictionary dictionaryWithContentsOfFile:fontFamilySavePath]; if (fontFamilyToFiles == nil) { fontFamilyToFiles = [NSMutableDictionary dictionary]; } } - if (urlToFile == nil) { - urlToFile = [NSMutableDictionary dictionaryWithContentsOfFile:fontUrlCachePath]; - if (urlToFile == nil) { - urlToFile = [NSMutableDictionary dictionary]; + if (urlToFilePath == nil) { + urlToFilePath = [NSMutableDictionary dictionaryWithContentsOfFile:fontUrlSavePath]; + if (urlToFilePath == nil) { + urlToFilePath = [NSMutableDictionary dictionary]; } } } @@ -110,16 +111,15 @@ - (void)loadFont:(NSNotification *)notification { + (NSString *)getFontPath:(NSString *)url { [self initDictIfNeeded]; - NSString *fontFile = urlToFile[url]; - if (!fontFile) { + NSString *fontFilePath = urlToFilePath[url]; + if (!fontFilePath) { return nil; } - NSString *fontPath = [fontDirPath stringByAppendingPathComponent:fontFile]; NSFileManager *fileManager = [NSFileManager defaultManager]; - if (![fileManager fileExistsAtPath:fontPath]) { + if (![fileManager fileExistsAtPath:fontFilePath]) { return nil; } - return fontPath; + return fontFilePath; } + (void)registerFontIfNeeded:(NSString *)fontFamily { @@ -130,9 +130,8 @@ + (void)registerFontIfNeeded:(NSString *)fontFamily { NSMutableArray *fileNotExist = [NSMutableArray array]; for (NSString *fontFile in fontFiles) { if (![fontRegistered containsObject:fontFile]) { - NSString *fontFilePath = [fontDirPath stringByAppendingPathComponent:fontFile]; NSError *error = nil; - if ([self registerFontFromURL:fontFilePath error:&error]) { + if ([self registerFontFromURL:fontFile error:&error]) { [fontRegistered addObject:fontFile]; isFontRegistered = YES; HippyLogInfo(@"register font \"%@\" success!", fontFile); @@ -173,19 +172,19 @@ + (BOOL)registerFontFromURL:(NSString *)urlString error:(NSError **)error { return YES; } -- (void)cacheFontfamily:(NSString *)fontFamily url:(NSString *)url fileName:(NSString *)fileName { +- (void)saveFontfamily:(NSString *)fontFamily url:(NSString *)url filePath:(NSString *)filePath { [HippyFontLoaderModule initDictIfNeeded]; - [urlToFile setObject:fileName forKey:url]; + [urlToFilePath setObject:filePath forKey:url]; NSMutableArray *fontFiles = [fontFamilyToFiles objectForKey:fontFamily]; if (!fontFiles) { - fontFiles = [NSMutableArray arrayWithObject:fileName]; + fontFiles = [NSMutableArray arrayWithObject:filePath]; [fontFamilyToFiles setObject:fontFiles forKey:fontFamily]; } else { - [fontFiles addObject:fileName]; + [fontFiles addObject:filePath]; } - [urlToFile writeToFile:fontUrlCachePath atomically:YES]; - [fontFamilyToFiles writeToFile:fontFamilyCachePath atomically:YES]; + [urlToFilePath writeToFile:fontUrlSavePath atomically:YES]; + [fontFamilyToFiles writeToFile:fontFamilySavePath atomically:YES]; } @@ -208,20 +207,6 @@ - (void)cacheFontfamily:(NSString *)fontFamily url:(NSString *)url fileName:(NSS [HippyFontLoaderModule setUrl:urlString state:HippyFontUrlLoading]; } - NSFileManager *fileManager = [NSFileManager defaultManager]; - if (![fileManager fileExistsAtPath:fontDirPath]) { - NSError *error; - [fileManager createDirectoryAtPath:fontDirPath withIntermediateDirectories:YES attributes:nil error:&error]; - if (error) { - NSString *errorKey = [NSString stringWithFormat:@"%lu", FontLoaderErrorDirectoryError]; - if (reject) { - reject(errorKey, @"directory create error", error); - } - [HippyFontLoaderModule setUrl:urlString state:HippyFontUrlFailed]; - return; - } - } - __weak __typeof(self) weakSelf = self; [self.bridge loadContentsAsynchronouslyFromUrl:urlString method:@"Get" @@ -239,24 +224,52 @@ - (void)cacheFontfamily:(NSString *)fontFamily url:(NSString *)url fileName:(NSS [HippyFontLoaderModule setUrl:urlString state:HippyFontUrlFailed]; return; } - NSString *fileName = [fontFamily stringByAppendingFormat:@".%@", [response.suggestedFilename pathExtension]]; - NSString *fontFilePath = [fontDirPath stringByAppendingPathComponent:fileName]; - if ([data writeToFile:fontFilePath atomically:YES]) { + // is local file url + if ([userInfo[HippyVFSResponseURLTypeKey] integerValue] == HippyVFSURLTypeFile) { + NSString *fontFilePath = userInfo[HippyVFSResponseAbsoluteURLStringKey] ?: urlString; dispatch_async([HippyFontLoaderModule getFontSerialQueue], ^{ - [strongSelf cacheFontfamily:fontFamily url:urlString fileName:fileName]; - [[NSNotificationCenter defaultCenter] postNotificationName:HippyFontChangeTriggerNotification object:nil]; + [strongSelf saveFontfamily:fontFamily url:urlString filePath:fontFilePath]; + [HippyFontLoaderModule registerFontIfNeeded:fontFamily]; }); if (resolve) { - resolve([NSString stringWithFormat:@"download font file \"%@\" success!", fileName]); + resolve([NSString stringWithFormat:@"load local font file \"%@\" success!", fontFilePath]); } [HippyFontLoaderModule setUrl:urlString state:HippyFontUrlLoaded]; } + // is http url else { - if (reject) { - NSString *errorKey = [NSString stringWithFormat:@"%lu", FontLoaderErrorWriteFileError]; - reject(errorKey, @"font request error", error); + NSFileManager *fileManager = [NSFileManager defaultManager]; + if (![fileManager fileExistsAtPath:fontDirPath]) { + NSError *error; + [fileManager createDirectoryAtPath:fontDirPath withIntermediateDirectories:YES attributes:nil error:&error]; + if (error) { + NSString *errorKey = [NSString stringWithFormat:@"%lu", FontLoaderErrorDirectoryError]; + if (reject) { + reject(errorKey, @"directory create error", error); + } + [HippyFontLoaderModule setUrl:urlString state:HippyFontUrlFailed]; + return; + } + } + NSString *fileName = [fontFamily stringByAppendingFormat:@".%@", [response.suggestedFilename pathExtension]]; + NSString *fontFilePath = [fontDirPath stringByAppendingPathComponent:fileName]; + if ([data writeToFile:fontFilePath atomically:YES]) { + dispatch_async([HippyFontLoaderModule getFontSerialQueue], ^{ + [strongSelf saveFontfamily:fontFamily url:urlString filePath:fontFilePath]; + [HippyFontLoaderModule registerFontIfNeeded:fontFamily]; + }); + if (resolve) { + resolve([NSString stringWithFormat:@"download font file \"%@\" success!", fileName]); + } + [HippyFontLoaderModule setUrl:urlString state:HippyFontUrlLoaded]; + } + else { + if (reject) { + NSString *errorKey = [NSString stringWithFormat:@"%lu", FontLoaderErrorWriteFileError]; + reject(errorKey, @"font request error", error); + } + [HippyFontLoaderModule setUrl:urlString state:HippyFontUrlFailed]; } - [HippyFontLoaderModule setUrl:urlString state:HippyFontUrlFailed]; } }]; } diff --git a/modules/vfs/ios/HippyFileHandler.mm b/modules/vfs/ios/HippyFileHandler.mm index cd308b70c49..cbc20884082 100644 --- a/modules/vfs/ios/HippyFileHandler.mm +++ b/modules/vfs/ios/HippyFileHandler.mm @@ -25,6 +25,9 @@ #include "HippyFileHandler.h" #include "footstone/logging.h" +NSString *const HippyVFSResponseAbsoluteURLStringKey = @"HippyVFSResponseAbsoluteURLStringKey"; +NSString *const HippyVFSResponseURLTypeKey = @"HippyVFSResponseURLTypeKey"; + HippyFileHandler::HippyFileHandler(HippyBridge *bridge) { bridge_ = bridge; } @@ -82,12 +85,12 @@ } HippyBridge *bridge = bridge_; if (!bridge || !request) { - completion(nil, nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnsupportedURL userInfo:nil]); + completion(nil, @{HippyVFSResponseURLTypeKey: @(HippyVFSURLTypeFile)}, nil, [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnsupportedURL userInfo:nil]); return; } NSURL *url = [request URL]; if (!url) { - completion(nil, nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnsupportedURL userInfo:nil]); + completion(nil, @{HippyVFSResponseURLTypeKey: @(HippyVFSURLTypeFile)}, nil, [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnsupportedURL userInfo:nil]); return; } @@ -100,7 +103,7 @@ MIMEType:nil expectedContentLength:fileData.length textEncodingName:nil]; - completion(fileData, nil, rsp, error); + completion(fileData, @{HippyVFSResponseURLTypeKey: @(HippyVFSURLTypeFile), HippyVFSResponseAbsoluteURLStringKey: [absoluteURL path]}, rsp, error); }; if (queue) { [queue addOperationWithBlock:opBlock]; @@ -109,6 +112,6 @@ } } else { FOOTSTONE_DLOG(ERROR) << "HippyFileHandler cannot load url " << [[absoluteURL absoluteString] UTF8String]; - completion(nil, nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnsupportedURL userInfo:nil]); + completion(nil, @{HippyVFSResponseURLTypeKey: @(HippyVFSURLTypeFile)}, nil, [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnsupportedURL userInfo:nil]); } } diff --git a/modules/vfs/ios/HippyVFSDefines.h b/modules/vfs/ios/HippyVFSDefines.h index ecadb88f2d2..b281b9c8520 100644 --- a/modules/vfs/ios/HippyVFSDefines.h +++ b/modules/vfs/ios/HippyVFSDefines.h @@ -39,6 +39,11 @@ enum HippyVFSRscType { HippyVFSRscTypeImage, }; +enum HippyVFSURLType { + HippyVFSURLTypeHTTP = 1, + HippyVFSURLTypeFile, +}; + // Resource Type Key for VFS Request in `extraInfo` parameter, // Value is defined in HippyVFSRscType @@ -53,6 +58,13 @@ FOUNDATION_EXPORT NSString *_Nonnull const kHippyVFSRequestExtraInfoForCustomIma // The image returned in `userInfo` parameter of VFSHandlerCompletionBlock FOUNDATION_EXPORT NSString *_Nonnull const HippyVFSResponseDecodedImageKey; +// The absolute file url returned in `userInfo` parameter of VFSHandlerCompletionBlock +FOUNDATION_EXPORT NSString *_Nonnull const HippyVFSResponseAbsoluteURLStringKey; + +// The type of url returned in `userInfo` parameter of VFSHandlerCompletionBlock +// Value is defined in HippyVFSURLType +FOUNDATION_EXPORT NSString *_Nonnull const HippyVFSResponseURLTypeKey; + typedef void(^VFSHandlerProgressBlock)(NSUInteger current, NSUInteger total); typedef void(^VFSHandlerCompletionBlock)(NSData *_Nullable data, diff --git a/modules/vfs/ios/VFSUriHandler.mm b/modules/vfs/ios/VFSUriHandler.mm index 89f5e465473..59597018f25 100644 --- a/modules/vfs/ios/VFSUriHandler.mm +++ b/modules/vfs/ios/VFSUriHandler.mm @@ -209,7 +209,7 @@ static bool CheckRequestFromCPP(const std::unordered_map(hippy::JobResponse::RetCode::SchemeNotRegister); NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:code userInfo:userInfo]; - completion(nil, nil, nil, error); + completion(nil, @{HippyVFSResponseURLTypeKey: @(HippyVFSURLTypeHTTP)}, nil, error); return; } NSURLSessionDataProgress *dataProgress = [[NSURLSessionDataProgress alloc] initWithProgress:progress result:completion]; @@ -247,7 +247,7 @@ static bool CheckRequestFromCPP(const std::unordered_map(hippy::JobResponse::RetCode::ResourceNotFound); NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:code userInfo:userInfo]; - completion(nil, nil, nil, error); + completion(nil, @{HippyVFSResponseURLTypeKey: @(HippyVFSURLTypeHTTP)}, nil, error); return; } auto progressCallback = [progress](int64_t current, int64_t total){ @@ -274,7 +274,7 @@ static bool CheckRequestFromCPP(const std::unordered_map(cb->GetRetCode()); error = [NSError errorWithDomain:NSURLErrorDomain code:code userInfo:userInfo]; } - completion(data, nil, response, error); + completion(data, @{HippyVFSResponseURLTypeKey: @(HippyVFSURLTypeHTTP)}, response, error); } }; loader->hippy::UriLoader::RequestUntrustedContent(requestJob, responseCallback); 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 d99f7348b9b..eca6dcb372a 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 @@ -803,11 +803,17 @@ private void updateTypeface() { mTextPaint.reset(); } NativeRender nativeRenderer = NativeRendererManager.getNativeRenderer(getContext()); - if (mFontUrl != null) { + if (mFontUrl != null && !mFontUrl.isEmpty()) { FontLoader loader = nativeRenderer == null ? null : nativeRenderer.getFontLoader(); if (loader != null) { int rootId = nativeRenderer.getRootView(this).getId(); - loader.loadIfNeeded(mFontFamily, mFontUrl, rootId); + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + loader.loadIfNeeded(mFontFamily, mFontUrl, rootId); + } + }); + thread.start(); } } FontAdapter fontAdapter = nativeRenderer == null ? null : nativeRenderer.getFontAdapter(); 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 index b830fdd0e3a..f332c65a95a 100644 --- 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 @@ -16,6 +16,10 @@ package com.tencent.renderer.component.text; +import static com.tencent.vfs.UrlUtils.PREFIX_ASSETS; +import static com.tencent.vfs.UrlUtils.PREFIX_FILE; + +import android.content.res.AssetManager; import android.text.TextUtils; import androidx.annotation.NonNull; @@ -25,6 +29,7 @@ import com.tencent.mtt.hippy.utils.LogUtils; import com.tencent.renderer.NativeRender; import com.tencent.vfs.ResourceDataHolder; +import com.tencent.vfs.UrlUtils; import com.tencent.vfs.VfsManager; import com.tencent.vfs.VfsManager.FetchResourceCallback; @@ -45,12 +50,15 @@ public class FontLoader { private final WeakReference mNativeRenderRef; private final WeakReference mVfsManager; private final File mFontDir; - private final File mFontUrlMapPath; - private Map mConcurrentFontUrlMap; + private final File mUrlFontMapFile; + private final File mLocalFontPathMapFile; + private static Map mConcurrentUrlFontMap; + private static Map mConcurrentLocalFontPathMap; private final Map mConcurrentFontLoadStateMap; private static final String[] ALLOWED_EXTENSIONS = {"otf", "ttf"}; - private static final String FONT_DIR_NAME = "HippyFonts"; - private static final String FONT_URL_MAP_NAME = "fontUrlMap.ser"; + private static final String FONT_DIR_NAME = "fonts"; + private static final String URL_FONT_MAP_NAME = "urlFontMap.ser"; + private static final String LOCAL_FONT_PATH_MAP_NAME = "localFontPathMap.ser"; public enum FontLoadState { FONT_LOADING, @@ -62,7 +70,8 @@ public FontLoader(VfsManager vfsManager, NativeRender nativeRender) { mNativeRenderRef = new WeakReference<>(nativeRender); mVfsManager = new WeakReference<>(vfsManager); mFontDir = new File(ContextHolder.getAppContext().getCacheDir(), FONT_DIR_NAME); - mFontUrlMapPath = new File(mFontDir, FONT_URL_MAP_NAME); + mUrlFontMapFile = new File(mFontDir, URL_FONT_MAP_NAME); + mLocalFontPathMapFile = new File(mFontDir, LOCAL_FONT_PATH_MAP_NAME); mConcurrentFontLoadStateMap = new ConcurrentHashMap<>(); } @@ -95,13 +104,13 @@ public boolean isFontLoaded(String fontFamily) { mConcurrentFontLoadStateMap.get(fontFamily) == FontLoadState.FONT_LOADED; } - private void saveFontUrlMapFile() { - try (FileOutputStream fos = new FileOutputStream(mFontUrlMapPath); + private void saveMapFile(File outputFile, Map map) { + try (FileOutputStream fos = new FileOutputStream(outputFile); ObjectOutputStream oos = new ObjectOutputStream(fos)) { - oos.writeObject(mConcurrentFontUrlMap); - LogUtils.d("FontLoader", "Save fontUrlMap.ser success"); + oos.writeObject(map); + LogUtils.d("FontLoader", String.format("Save %s success!", outputFile.getName())); } catch (IOException e) { - LogUtils.d("FontLoader", "Save fontUrlMap.ser failed"); + LogUtils.d("FontLoader", String.format("Save %s failed!", outputFile.getName())); } } @@ -116,20 +125,83 @@ public static String getFileExtension(String url) { return ""; } - public boolean loadIfNeeded(final String fontFamily, final String fontUrl, int rootId) { - if (mConcurrentFontUrlMap == null) { - Map fontUrlMap; - try (FileInputStream fis = new FileInputStream(mFontUrlMapPath); - ObjectInputStream ois = new ObjectInputStream(fis)) { - fontUrlMap = (Map) ois.readObject(); - } catch (IOException | ClassNotFoundException e) { - fontUrlMap = new HashMap<>(); + public static String getFontPath(String fontFileName) { + if (mConcurrentLocalFontPathMap != null && fontFileName != null) { + return mConcurrentLocalFontPathMap.get(fontFileName); + } + return null; + } + + // Convert "hpfile://" to "file://" or "assets://" + private String convertToLocalPathIfNeeded(String fontUrl) { + if (fontUrl != null && fontUrl.startsWith("hpfile://")) { + String bundlePath = mNativeRenderRef.get().getBundlePath(); + String relativePath = fontUrl.replace("hpfile://./", ""); + fontUrl = bundlePath == null ? null + : bundlePath.subSequence(0, bundlePath.lastIndexOf(File.separator) + 1) + + relativePath; + } + return fontUrl; + } + + private Map readMapFromFile(File file) { + Map map; + try (FileInputStream fis = new FileInputStream(file); + ObjectInputStream ois = new ObjectInputStream(fis)) { + map = (Map) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + map = new HashMap<>(); + } + return map; + } + + private boolean isAssetFileExists(String assetFilePath) { + AssetManager assetManager = ContextHolder.getAppContext().getAssets(); + String directory = ""; + String fileName = assetFilePath; + if (fileName.startsWith(PREFIX_ASSETS)) { + fileName = fileName.substring(PREFIX_ASSETS.length()); + } + int lastSlashIndex = assetFilePath.lastIndexOf(File.separator); + if (lastSlashIndex != -1) { + directory = assetFilePath.substring(0, lastSlashIndex); + fileName = assetFilePath.substring(lastSlashIndex + 1); + } + try { + String[] files = assetManager.list(directory); + if (files != null) { + for (String file : files) { + if (file.equals(fileName)) { + return true; + } + } } - mConcurrentFontUrlMap = new ConcurrentHashMap<>(fontUrlMap); + } catch (IOException e) { + LogUtils.d("FontLoader", String.format("Find directory %s failed", directory)); } - String fontFileName = mConcurrentFontUrlMap.get(fontUrl); + return false; + } + + private void initMapIfNeeded() { + if (mConcurrentUrlFontMap == null) { + mConcurrentUrlFontMap = new ConcurrentHashMap<>(readMapFromFile(mUrlFontMapFile)); + } + if (mConcurrentLocalFontPathMap == null) { + mConcurrentLocalFontPathMap = new ConcurrentHashMap<>(readMapFromFile(mLocalFontPathMapFile)); + } + } + + public boolean loadIfNeeded(final String fontFamily, final String fontUrl, int rootId) { + initMapIfNeeded(); + String fontFileName = mConcurrentUrlFontMap.get(fontUrl); if (fontFileName != null) { - File fontFile = new File(mFontDir, fontFileName); + if (fontFileName.startsWith(PREFIX_ASSETS) && isAssetFileExists(fontFileName)) { + return false; + } + if (fontFileName.startsWith(PREFIX_FILE)) { + fontFileName = fontFileName.substring(PREFIX_FILE.length()); + } + File fontFile = new File(fontFileName); if (fontFile.exists()) { return false; } @@ -152,7 +224,8 @@ public void loadAndRefresh(final String fontFamily, final String fontUrl, int ro return; } mConcurrentFontLoadStateMap.put(fontFamily, FontLoadState.FONT_LOADING); - mVfsManager.get().fetchResourceAsync(fontUrl, null, null, + String convertFontUrl = convertToLocalPathIfNeeded(fontUrl); + mVfsManager.get().fetchResourceAsync(convertFontUrl, null, null, new FetchResourceCallback() { @Override public void onFetchCompleted(@NonNull final ResourceDataHolder dataHolder) { @@ -165,20 +238,42 @@ public void onFetchCompleted(@NonNull final ResourceDataHolder dataHolder) { promise.reject("Fetch font file failed, url=" + fontUrl); } } else { - String fileName = fontFamily + getFileExtension(fontUrl); - if (saveFontFile(bytes, fileName, promise)) { - LogUtils.d("FontLoader", "Fetch font file success"); - mConcurrentFontLoadStateMap.put(fontFamily, FontLoadState.FONT_LOADED); - mConcurrentFontUrlMap.put(fontUrl, fileName); - saveFontUrlMapFile(); - TypeFaceUtil.clearFontCache(fontFamily); - NativeRender nativeRender = mNativeRenderRef.get(); - if (nativeRender != null) { - nativeRender.refreshTextWindow(rootId); + initMapIfNeeded(); + boolean needRefresh = true; + if (UrlUtils.isFileUrl(convertFontUrl) || UrlUtils.isAssetsUrl(convertFontUrl)) { + mConcurrentUrlFontMap.put(fontUrl, convertFontUrl); + String fileName = fontFamily + getFileExtension(fontUrl); + if (convertFontUrl.equals(mConcurrentLocalFontPathMap.get(fileName))) { + needRefresh = false; + } + else { + mConcurrentLocalFontPathMap.put(fileName, convertFontUrl); + } + if (promise != null) { + promise.resolve(String.format("Load local font %s success", convertFontUrl)); } } else { - mConcurrentFontLoadStateMap.remove(fontFamily); + String fileName = fontFamily + getFileExtension(fontUrl); + if (!saveFontFile(bytes, fileName, promise)) { + mConcurrentFontLoadStateMap.remove(fontFamily); + return; + } + else { + File fontFile = new File(mFontDir, fileName); + mConcurrentUrlFontMap.put(fontUrl, fontFile.getAbsolutePath()); + if (promise != null) { + promise.resolve(String.format("Download font %s success", fileName)); + } + } + } + mConcurrentFontLoadStateMap.put(fontFamily, FontLoadState.FONT_LOADED); + saveMapFile(mUrlFontMapFile, mConcurrentUrlFontMap); + saveMapFile(mLocalFontPathMapFile, mConcurrentLocalFontPathMap); + TypeFaceUtil.clearFontCache(fontFamily); + NativeRender nativeRender = mNativeRenderRef.get(); + if (nativeRender != null && needRefresh) { + nativeRender.refreshTextWindow(rootId); } } dataHolder.recycle(); 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 0dd8b93ea97..f4a00a65af9 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 @@ -27,6 +27,7 @@ import com.tencent.mtt.hippy.utils.ContextHolder; import com.tencent.mtt.hippy.utils.LogUtils; +import com.tencent.vfs.UrlUtils; import java.io.File; import java.util.HashMap; @@ -42,7 +43,7 @@ public class TypeFaceUtil { 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 FONTS_PATH = "HippyFonts/"; + private static final String FONTS_PATH = "fonts/"; private static final Map> sFontCache = new HashMap<>(); /** @@ -88,7 +89,7 @@ private static Typeface createExactTypeFace(String fileName) { // create from assets Typeface typeface = null; try { - typeface = Typeface.createFromAsset(ContextHolder.getAppContext().getAssets(), fileName); + typeface = Typeface.createFromAsset(ContextHolder.getAppContext().getAssets(), FONTS_PATH+fileName); } catch (Exception e) { LogUtils.w(TAG, e.getMessage()); } @@ -96,11 +97,35 @@ private static Typeface createExactTypeFace(String fileName) { if (typeface == null || typeface.equals(Typeface.DEFAULT)) { try { File cacheDir = ContextHolder.getAppContext().getCacheDir(); - typeface = Typeface.createFromFile(new File(cacheDir, fileName)); + typeface = Typeface.createFromFile(new File(cacheDir, FONTS_PATH+fileName)); } catch (Exception e) { LogUtils.w(TAG, e.getMessage()); } } + // create from local bundle file + if (typeface == null || typeface.equals(Typeface.DEFAULT)) { + String bundleFontPath = FontLoader.getFontPath(fileName); + if (bundleFontPath != null) { + if (bundleFontPath.startsWith(UrlUtils.PREFIX_ASSETS)) { + try { + typeface = Typeface.createFromAsset(ContextHolder.getAppContext().getAssets(), + bundleFontPath.substring(UrlUtils.PREFIX_ASSETS.length())); + } catch (Exception e) { + LogUtils.w(TAG, e.getMessage()); + } + } + else { + if (bundleFontPath.startsWith(UrlUtils.PREFIX_FILE)) { + bundleFontPath = bundleFontPath.substring(UrlUtils.PREFIX_FILE.length()); + } + try { + typeface = Typeface.createFromFile(new File(bundleFontPath)); + } catch (Exception e) { + LogUtils.w(TAG, e.getMessage()); + } + } + } + } return typeface; } @@ -118,7 +143,7 @@ private static Typeface createTypeface(String fontFamilyName, int weightNumber, continue; } for (String fileExtension : FONT_EXTENSIONS) { - String fileName = FONTS_PATH + splitName + extension + fileExtension; + String fileName = splitName + extension + fileExtension; Typeface typeface = createExactTypeFace(fileName); if (typeface != null && !typeface.equals(Typeface.DEFAULT)) { return typeface; 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 e9babd910f9..4129b476adb 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 @@ -489,7 +489,13 @@ protected void createSpanOperationImpl(@NonNull List ops, size = (int) (size * mFontAdapter.getFontScale()); } if (mFontUrl != null && !mFontUrl.isEmpty() && mFontLoaderRef.get() != null) { - mFontLoaderRef.get().loadIfNeeded(mFontFamily, mFontUrl, getRootId()); + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + mFontLoaderRef.get().loadIfNeeded(mFontFamily, mFontUrl, getRootId()); + } + }); + thread.start(); } ops.add(new SpanOperation(start, end, new AbsoluteSizeSpan(size))); ops.add(new SpanOperation(start, end, new TextStyleSpan(mItalic, mFontWeight, mFontFamily, mFontAdapter))); From 074c23854742f6ba537637a2506916dfdda47e55 Mon Sep 17 00:00:00 2001 From: arnonchen <745735906@qq.com> Date: Thu, 24 Oct 2024 19:45:29 +0800 Subject: [PATCH 11/20] feat(ios): add unitest for FontLoaderModule --- .../src/modules/FontLoader/index.jsx | 1 + .../module/fontLoader/HippyFontLoaderModule.h | 11 +- .../fontLoader/HippyFontLoaderModule.mm | 5 +- tests/ios/HippyFontLoaderTest.m | 119 ++++++++++++++++++ 4 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 tests/ios/HippyFontLoaderTest.m diff --git a/driver/js/examples/hippy-react-demo/src/modules/FontLoader/index.jsx b/driver/js/examples/hippy-react-demo/src/modules/FontLoader/index.jsx index f204779e6f7..73eae1a9fb2 100644 --- a/driver/js/examples/hippy-react-demo/src/modules/FontLoader/index.jsx +++ b/driver/js/examples/hippy-react-demo/src/modules/FontLoader/index.jsx @@ -72,6 +72,7 @@ export default class LoadFontExample extends React.Component { fillExample() { this.setState({ inputFontFamily: 'HYHuaXianZi J' }); this.setState({ fontUrl: 'https://zf.sc.chinaz.com/Files/DownLoad/upload/2024/1009/hanyihuaxianzijianti.ttf' }); + // this.setState({ fontUrl: 'hpfile://./assets/hanyihuaxianzijianti.ttf' } } async loadFont() { diff --git a/framework/ios/module/fontLoader/HippyFontLoaderModule.h b/framework/ios/module/fontLoader/HippyFontLoaderModule.h index 8abad6de53b..df1115adf48 100644 --- a/framework/ios/module/fontLoader/HippyFontLoaderModule.h +++ b/framework/ios/module/fontLoader/HippyFontLoaderModule.h @@ -48,7 +48,7 @@ typedef NS_ENUM(NSInteger, HippyFontUrlState) { * * @param fontFamily - The font family needs to be registered */ -+ (void)registerFontIfNeeded:(NSString *)fontFamily; ++ (BOOL)registerFontIfNeeded:(NSString *)fontFamily; /** * Whether the font is downloading from the url. @@ -65,6 +65,15 @@ typedef NS_ENUM(NSInteger, HippyFontUrlState) { */ + (dispatch_queue_t) getFontSerialQueue; +/** + * Load font from the url. + * + * @param urlString - The url where font file is downloaded. + * @param resolve - The callback block for downloading successful. + * @param reject - The callback block for downloading failed. + */ +- (void) load:(NSString *)fontFamily from:(NSString *)urlString resolver:(nullable HippyPromiseResolveBlock)resolve rejecter:(nullable HippyPromiseRejectBlock)reject; + @end NS_ASSUME_NONNULL_END diff --git a/framework/ios/module/fontLoader/HippyFontLoaderModule.mm b/framework/ios/module/fontLoader/HippyFontLoaderModule.mm index f77792166e2..9607b505620 100644 --- a/framework/ios/module/fontLoader/HippyFontLoaderModule.mm +++ b/framework/ios/module/fontLoader/HippyFontLoaderModule.mm @@ -45,7 +45,7 @@ static dispatch_queue_t serialQueue; static NSMutableDictionary *urlToFilePath; static NSMutableDictionary *fontFamilyToFiles; -static NSMutableDictionary *urlLoadState; +static NSMutableDictionary *urlLoadState = [NSMutableDictionary dictionary]; static NSMutableArray *fontRegistered = [NSMutableArray array]; static NSString *fontDirPath; static NSString *fontUrlSavePath; @@ -122,7 +122,7 @@ + (NSString *)getFontPath:(NSString *)url { return fontFilePath; } -+ (void)registerFontIfNeeded:(NSString *)fontFamily { ++ (BOOL)registerFontIfNeeded:(NSString *)fontFamily { [self initDictIfNeeded]; NSMutableArray *fontFiles = [fontFamilyToFiles objectForKey:fontFamily]; BOOL isFontRegistered = NO; @@ -149,6 +149,7 @@ + (void)registerFontIfNeeded:(NSString *)fontFamily { [[NSNotificationCenter defaultCenter] postNotificationName:HippyFontChangeTriggerNotification object:nil]; } } + return isFontRegistered; } diff --git a/tests/ios/HippyFontLoaderTest.m b/tests/ios/HippyFontLoaderTest.m new file mode 100644 index 00000000000..6662c7473b8 --- /dev/null +++ b/tests/ios/HippyFontLoaderTest.m @@ -0,0 +1,119 @@ +/*! + * 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 +#import + +@interface HippyFontLoaderTest : XCTestCase + +@property (nonatomic, strong) HippyFontLoaderModule *fontLoader; + +@end + +@implementation HippyFontLoaderTest + +- (void)setUp { + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. +} + +- (void)testHippyFontLoaderModule { + NSString* invalidURL = @"https://example.url"; + // set arbitrary valid font file url + NSString* validURL = @"https://zf.sc.chinaz.com/Files/DownLoad/upload/2024/1009/hanyihuaxianzijianti.ttf"; + NSString* fontFamily = @"HYHuaXianZi J"; + HippyBridge *bridge = [[HippyBridge alloc] initWithDelegate:nil moduleProvider:nil launchOptions:nil executorKey:nil]; + HippyFontLoaderModule *fontLoader = [[HippyFontLoaderModule alloc] init]; + [fontLoader setValue:bridge forKey:@"bridge"]; + + // test fetch from invalidURL + XCTestExpectation *invalidURLExpectation = [self expectationWithDescription:@"Fetch data from invalid url expectation"]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), + [HippyFontLoaderModule getFontSerialQueue], ^{ + [fontLoader load:fontFamily from:invalidURL resolver:^(id result) { + [invalidURLExpectation fulfill]; + } rejecter:^(NSString *code, NSString *message, NSError *error) { + // test whether the url is loading + XCTAssertTrue([HippyFontLoaderModule isUrlLoading:invalidURL]); + XCTAssertEqual(message, @"font request error"); + [invalidURLExpectation fulfill]; + }]; + }); + [self waitForExpectationsWithTimeout:5 handler:nil]; + + // test fetch from validURL + XCTestExpectation *validURLExpectation = [self expectationWithDescription:@"Fetch data from valid url expectation"]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), + [HippyFontLoaderModule getFontSerialQueue], ^{ + [fontLoader load:fontFamily from:validURL resolver:^(id result) { + // test whether the url is loading + XCTAssertTrue([HippyFontLoaderModule isUrlLoading:validURL]); + [validURLExpectation fulfill]; + } rejecter:^(NSString *code, NSString *message, NSError *error) { + XCTAssert(true, @"fetch valid url failed"); + [validURLExpectation fulfill]; + }]; + }); + [self waitForExpectationsWithTimeout:5 handler:nil]; + + __block NSString *fontPath; + // test get font path using undownloaded font + XCTestExpectation *undownloadedExpectation = [self expectationWithDescription:@"get undownloaded font path expectation"]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), + [HippyFontLoaderModule getFontSerialQueue], ^{ + fontPath = [HippyFontLoaderModule getFontPath:invalidURL]; + XCTAssertNil(fontPath); + [undownloadedExpectation fulfill]; + }); + [self waitForExpectationsWithTimeout:5 handler:nil]; + + // test get font path using downloaded font + XCTestExpectation *downloadedExpectation = [self expectationWithDescription:@"get downloaded font path expectation"]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), + [HippyFontLoaderModule getFontSerialQueue], ^{ + fontPath = [HippyFontLoaderModule getFontPath:validURL]; + XCTAssertNotNil(fontPath); + [downloadedExpectation fulfill]; + }); + [self waitForExpectationsWithTimeout:5 handler:nil]; + + // test whether font registered successfully in load method + BOOL needRegister = [HippyFontLoaderModule registerFontIfNeeded:fontFamily]; + XCTAssertFalse(needRegister); + + // delete font directory + [[NSFileManager defaultManager] removeItemAtPath:[fontPath stringByDeletingLastPathComponent] error:nil]; +} + +- (void)testPerformanceExample { + // This is an example of a performance test case. + [self measureBlock:^{ + // Put the code you want to measure the time of here. + }]; +} + +@end From 15e1fdfdd696e28f918dfc9e1126a42f1c02331a Mon Sep 17 00:00:00 2001 From: arnonchen <745735906@qq.com> Date: Thu, 24 Oct 2024 22:23:35 +0800 Subject: [PATCH 12/20] fix(android, ios): improve dynamically load font according to code review --- .../openhippy/connector/NativeRenderer.java | 6 ++-- .../module/fontLoader/HippyFontLoaderModule.h | 6 ++-- .../fontLoader/HippyFontLoaderModule.mm | 8 ++--- .../cpp/include/renderer/native_render_jni.h | 2 +- .../cpp/src/renderer/native_render_jni.cc | 12 ++++---- .../hippy/views/textinput/HippyTextInput.java | 25 ++++++++-------- .../com/tencent/renderer/NativeRender.java | 2 +- .../renderer/NativeRenderProvider.java | 6 ++-- .../com/tencent/renderer/NativeRenderer.java | 9 +++--- .../renderer/component/text/FontLoader.java | 19 +++++++++--- .../renderer/node/TextVirtualNode.java | 30 ++++++++++++------- 11 files changed, 72 insertions(+), 53 deletions(-) diff --git a/framework/android/connector/renderer/native/src/main/java/com/openhippy/connector/NativeRenderer.java b/framework/android/connector/renderer/native/src/main/java/com/openhippy/connector/NativeRenderer.java index f47b419af46..d87cac6e6a9 100644 --- a/framework/android/connector/renderer/native/src/main/java/com/openhippy/connector/NativeRenderer.java +++ b/framework/android/connector/renderer/native/src/main/java/com/openhippy/connector/NativeRenderer.java @@ -147,11 +147,9 @@ public void destroy() { @Override public void loadFontAndRefreshWindow(@NonNull String fontFamily, @NonNull String fontUrl, int rootId, Object promise) { - if (!(promise instanceof Promise)) { - promise = null; - } + Promise pm = (promise instanceof Promise) ? ((Promise) promise) : null; if (mRenderer != null) { - mRenderer.loadFontAndRefreshWindow(fontFamily, fontUrl, rootId, (Promise) promise); + mRenderer.loadFontAndRefreshWindow(fontFamily, fontUrl, rootId, pm); } } diff --git a/framework/ios/module/fontLoader/HippyFontLoaderModule.h b/framework/ios/module/fontLoader/HippyFontLoaderModule.h index df1115adf48..a6124758517 100644 --- a/framework/ios/module/fontLoader/HippyFontLoaderModule.h +++ b/framework/ios/module/fontLoader/HippyFontLoaderModule.h @@ -56,14 +56,14 @@ typedef NS_ENUM(NSInteger, HippyFontUrlState) { * @param url - The font url needs to download from. * @return Yes if the font is downloading from the url. */ -+ (BOOL) isUrlLoading:(NSString *)url; ++ (BOOL)isUrlLoading:(NSString *)url; /** * Get the serial queue in HippyFontLoaderModule for asyn serial operations. * * @return The serial dispatch_queue_t. */ -+ (dispatch_queue_t) getFontSerialQueue; ++ (dispatch_queue_t)getFontSerialQueue; /** * Load font from the url. @@ -72,7 +72,7 @@ typedef NS_ENUM(NSInteger, HippyFontUrlState) { * @param resolve - The callback block for downloading successful. * @param reject - The callback block for downloading failed. */ -- (void) load:(NSString *)fontFamily from:(NSString *)urlString resolver:(nullable HippyPromiseResolveBlock)resolve rejecter:(nullable HippyPromiseRejectBlock)reject; +- (void)load:(NSString *)fontFamily from:(NSString *)urlString resolver:(nullable HippyPromiseResolveBlock)resolve rejecter:(nullable HippyPromiseRejectBlock)reject; @end diff --git a/framework/ios/module/fontLoader/HippyFontLoaderModule.mm b/framework/ios/module/fontLoader/HippyFontLoaderModule.mm index 9607b505620..7bbb47b7fc4 100644 --- a/framework/ios/module/fontLoader/HippyFontLoaderModule.mm +++ b/framework/ios/module/fontLoader/HippyFontLoaderModule.mm @@ -76,19 +76,19 @@ - (instancetype)init { return self; } -+ (dispatch_queue_t) getFontSerialQueue { ++ (dispatch_queue_t)getFontSerialQueue { return serialQueue; } -+ (void) setUrl:(NSString *)url state:(HippyFontUrlState)state { ++ (void)setUrl:(NSString *)url state:(HippyFontUrlState)state { [urlLoadState setObject:@(state) forKey:url]; } -+ (BOOL) isUrlLoading:(NSString *)url { ++ (BOOL)isUrlLoading:(NSString *)url { return [[urlLoadState objectForKey:url] integerValue] == HippyFontUrlLoading; } -+ (void) initDictIfNeeded { ++ (void)initDictIfNeeded { if (fontFamilyToFiles == nil) { fontFamilyToFiles = [NSMutableDictionary dictionaryWithContentsOfFile:fontFamilySavePath]; if (fontFamilyToFiles == nil) { 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 205c48bc1b5..a9d5146c098 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 @@ -42,7 +42,7 @@ jobject GetNativeRendererInstance(JNIEnv* j_env, jint j_render_manager_id); -void RefreshTextWindow(JNIEnv *j_env, jobject j_object, jint j_render_manager_id, jint j_root_id); +void OnFontLoaded(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 9120761f005..97fa4f18cc6 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 @@ -78,9 +78,9 @@ REGISTER_JNI("com/tencent/renderer/NativeRenderProvider", UpdateRootSize) REGISTER_JNI("com/tencent/renderer/NativeRenderProvider", - "refreshTextWindow", + "onFontLoaded", "(II)V", - RefreshTextWindow) + OnFontLoaded) static jint JNI_OnLoad(__unused JavaVM* j_vm, __unused void* reserved) { auto j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); @@ -168,17 +168,17 @@ void MarkTextNodeDirtyRecursive(const std::shared_ptr& node) { } } -void RefreshTextWindow(JNIEnv *j_env, jobject j_object, jint j_render_manager_id, jint j_root_id) { +void OnFontLoaded(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) << "RefreshTextWindow j_render_manager_id invalid"; + FOOTSTONE_DLOG(WARNING) << "OnFontLoaded j_render_manager_id invalid"; return; } std::shared_ptr dom_manager = render_manager->GetDomManager(); if (dom_manager == nullptr) { - FOOTSTONE_DLOG(WARNING) << "RefreshTextWindow dom_manager is nullptr"; + FOOTSTONE_DLOG(WARNING) << "OnFontLoaded dom_manager is nullptr"; return; } auto& root_map = RootNode::PersistentMap(); @@ -186,7 +186,7 @@ void RefreshTextWindow(JNIEnv *j_env, jobject j_object, jint j_render_manager_id 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) << "RefreshTextWindow root_node is nullptr"; + FOOTSTONE_DLOG(WARNING) << "OnFontLoaded root_node is nullptr"; return; } 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 eca6dcb372a..b4063cb4076 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 @@ -65,6 +65,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.concurrent.Executor; @SuppressWarnings({"deprecation", "unused"}) public class HippyTextInput extends AppCompatEditText implements HippyViewBase, @@ -100,7 +101,7 @@ public class HippyTextInput extends AppCompatEditText implements HippyViewBase, private String mFontFamily; private String mFontUrl; private Paint mTextPaint; - protected boolean mFromFontLoader = false; + private FontLoader.FontLoadState mFontLoadState; public HippyTextInput(Context context) { super(context); @@ -214,9 +215,10 @@ public void onBatchComplete() { if (nativeRenderer != null) { fontLoader = nativeRenderer.getFontLoader(); } - if (!mFromFontLoader && fontLoader != null && fontLoader.isFontLoaded(mFontFamily)) { + if (mFontLoadState == FontLoader.FontLoadState.FONT_UNLOAD && fontLoader != null && + fontLoader.isFontLoaded(mFontFamily)) { mShouldUpdateTypeface = true; - mFromFontLoader = true; + mFontLoadState = FontLoader.FontLoadState.FONT_LOADED; } if (mShouldUpdateTypeface) { updateTypeface(); @@ -776,8 +778,8 @@ public void setFontFamily(String family) { if (nativeRenderer != null) { fontLoader = nativeRenderer.getFontLoader(); } - if (mFromFontLoader && fontLoader != null && !fontLoader.isFontLoaded(mFontFamily)) { - mFromFontLoader = false; + if (fontLoader != null && !fontLoader.isFontLoaded(mFontFamily)) { + mFontLoadState = FontLoader.FontLoadState.FONT_UNLOAD; } } } @@ -803,17 +805,16 @@ private void updateTypeface() { mTextPaint.reset(); } NativeRender nativeRenderer = NativeRendererManager.getNativeRenderer(getContext()); - if (mFontUrl != null && !mFontUrl.isEmpty()) { + if (!TextUtils.isEmpty(mFontUrl)) { FontLoader loader = nativeRenderer == null ? null : nativeRenderer.getFontLoader(); if (loader != null) { int rootId = nativeRenderer.getRootView(this).getId(); - Thread thread = new Thread(new Runnable() { - @Override - public void run() { + Executor executor = nativeRenderer.getBackgroundExecutor(); + if (executor != null) { + executor.execute(() -> { loader.loadIfNeeded(mFontFamily, mFontUrl, rootId); - } - }); - thread.start(); + }); + } } } FontAdapter fontAdapter = nativeRenderer == null ? null : nativeRenderer.getFontAdapter(); 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 bd83d2df238..1bd76c87640 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 @@ -108,7 +108,7 @@ 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 refreshTextWindow(int rootId); + void onFontLoaded(int rootId); void updateDimension(int width, int height); 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 edf80284256..a1da73cd184 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,8 +337,8 @@ public void onSizeChanged(int rootId, int width, int height) { updateRootSize(mInstanceId, rootId, PixelUtil.px2dp(width), PixelUtil.px2dp(height)); } - public void refreshTextWindow(int rootId) { - refreshTextWindow(mInstanceId, rootId); + public void onFontLoaded(int rootId) { + onFontLoaded(mInstanceId, rootId); } public void onSizeChanged(int rootId, int nodeId, int width, int height, boolean isSync) { @@ -445,7 +445,7 @@ private void dispatchEventImpl(int rootId, int nodeId, @NonNull String eventName * @param instanceId the unique id of native (C++) render manager */ @SuppressWarnings("JavaJniMissingFunction") - private native void refreshTextWindow(int instanceId, int rootId); + private native void onFontLoaded(int instanceId, int rootId); /** * Updates the size to the specified node, such as modal node, should set new window size before 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 efc18bb1fef..58c1df38c83 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 @@ -225,9 +225,8 @@ public ImageLoaderAdapter getImageLoader() { return mImageLoader; } - @Nullable public FontLoader getFontLoader() { - if (mFontLoader == null && getVfsManager() != null) { + if (mFontLoader == null) { mFontLoader = new FontLoader(getVfsManager(), this); } return mFontLoader; @@ -416,8 +415,8 @@ private void onSizeChanged(int rootId, int w, int h) { mRenderProvider.onSizeChanged(rootId, w, h); } - public void refreshTextWindow(int rootId) { - mRenderProvider.refreshTextWindow(rootId); + public void onFontLoaded(int rootId) { + mRenderProvider.onFontLoaded(rootId); } @Override @@ -1144,7 +1143,7 @@ private boolean collectNodeInfo(@NonNull RenderNode child, int pid, int outerLef @Override public void loadFontAndRefreshWindow(@NonNull String fontFamily, @NonNull String fontUrl, int rootId, final Promise promise) { - if (mFontLoader == null && getVfsManager() != null) { + if (mFontLoader == null) { mFontLoader = new FontLoader(getVfsManager(), this); } mFontLoader.loadAndRefresh(fontFamily, fontUrl, rootId, promise); 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 index f332c65a95a..d3d553a2672 100644 --- 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 @@ -61,8 +61,9 @@ public class FontLoader { private static final String LOCAL_FONT_PATH_MAP_NAME = "localFontPathMap.ser"; public enum FontLoadState { + FONT_UNLOAD, FONT_LOADING, - FONT_LOADED + FONT_LOADED, } @@ -193,6 +194,9 @@ private void initMapIfNeeded() { public boolean loadIfNeeded(final String fontFamily, final String fontUrl, int rootId) { initMapIfNeeded(); + if (TextUtils.isEmpty(fontFamily) || TextUtils.isEmpty(fontUrl)) { + return false; + } String fontFileName = mConcurrentUrlFontMap.get(fontUrl); if (fontFileName != null) { if (fontFileName.startsWith(PREFIX_ASSETS) && isAssetFileExists(fontFileName)) { @@ -206,7 +210,8 @@ public boolean loadIfNeeded(final String fontFamily, final String fontUrl, int r return false; } } - if (mConcurrentFontLoadStateMap.containsKey(fontFamily)) { + FontLoadState state = mConcurrentFontLoadStateMap.get(fontFamily); + if (state == FontLoadState.FONT_LOADING || state == FontLoadState.FONT_LOADED) { return false; } loadAndRefresh(fontFamily, fontUrl, rootId, null); @@ -225,6 +230,12 @@ public void loadAndRefresh(final String fontFamily, final String fontUrl, int ro } mConcurrentFontLoadStateMap.put(fontFamily, FontLoadState.FONT_LOADING); String convertFontUrl = convertToLocalPathIfNeeded(fontUrl); + if (mVfsManager.get() == null) { + if (promise != null) { + promise.reject("Get vfsManager failed!"); + } + return; + } mVfsManager.get().fetchResourceAsync(convertFontUrl, null, null, new FetchResourceCallback() { @Override @@ -233,7 +244,7 @@ public void onFetchCompleted(@NonNull final ResourceDataHolder dataHolder) { if (dataHolder.resultCode != ResourceDataHolder.RESOURCE_LOAD_SUCCESS_CODE || bytes == null || bytes.length <= 0) { - mConcurrentFontLoadStateMap.remove(fontFamily); + mConcurrentFontLoadStateMap.put(fontFamily, FontLoadState.FONT_UNLOAD); if (promise != null) { promise.reject("Fetch font file failed, url=" + fontUrl); } @@ -273,7 +284,7 @@ public void onFetchCompleted(@NonNull final ResourceDataHolder dataHolder) { TypeFaceUtil.clearFontCache(fontFamily); NativeRender nativeRender = mNativeRenderRef.get(); if (nativeRender != null && needRefresh) { - nativeRender.refreshTextWindow(rootId); + nativeRender.onFontLoaded(rootId); } } dataHolder.recycle(); 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 4129b476adb..91f9ad132f5 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 @@ -35,9 +35,11 @@ import android.text.style.AbsoluteSizeSpan; import android.text.style.BackgroundColorSpan; import android.text.style.ImageSpan; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; + import com.tencent.mtt.hippy.annotation.HippyControllerProps; import com.tencent.mtt.hippy.dom.node.NodeProps; import com.tencent.mtt.hippy.utils.I18nUtil; @@ -63,6 +65,7 @@ import java.util.HashMap; import java.util.List; import java.util.Objects; +import java.util.concurrent.Executor; public class TextVirtualNode extends VirtualNode { @@ -117,7 +120,8 @@ public class TextVirtualNode extends VirtualNode { protected String mFontUrl; @Nullable protected WeakReference mFontLoaderRef; - protected boolean mFromFontLoader = false; + protected WeakReference mNativeRenderRef; + protected FontLoader.FontLoadState mFontLoadState; @Nullable protected SpannableStringBuilder mSpanned; @Nullable @@ -139,6 +143,7 @@ public TextVirtualNode(int rootId, int id, int pid, int index, super(rootId, id, pid, index); mFontAdapter = nativeRender.getFontAdapter(); mFontLoaderRef = new WeakReference<>(nativeRender.getFontLoader()); + mNativeRenderRef = new WeakReference<>(nativeRender); if (I18nUtil.isRTL()) { mAlignment = Layout.Alignment.ALIGN_OPPOSITE; } @@ -188,6 +193,9 @@ public void setFontFamily(String family) { if (!Objects.equals(mFontFamily, family)) { mFontFamily = family; markDirty(); + if (mFontLoaderRef.get() != null && !mFontLoaderRef.get().isFontLoaded(mFontFamily)) { + mFontLoadState = FontLoader.FontLoadState.FONT_UNLOAD; + } } } @@ -488,14 +496,15 @@ protected void createSpanOperationImpl(@NonNull List ops, if (mFontAdapter != null && mEnableScale) { size = (int) (size * mFontAdapter.getFontScale()); } - if (mFontUrl != null && !mFontUrl.isEmpty() && mFontLoaderRef.get() != null) { - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - mFontLoaderRef.get().loadIfNeeded(mFontFamily, mFontUrl, getRootId()); + if (!TextUtils.isEmpty(mFontUrl) && mFontLoaderRef.get() != null) { + if (mNativeRenderRef.get() != null) { + Executor executor = mNativeRenderRef.get().getBackgroundExecutor(); + if (executor != null) { + executor.execute(() -> { + mFontLoaderRef.get().loadIfNeeded(mFontFamily, mFontUrl, getRootId()); + }); } - }); - thread.start(); + } } ops.add(new SpanOperation(start, end, new AbsoluteSizeSpan(size))); ops.add(new SpanOperation(start, end, new TextStyleSpan(mItalic, mFontWeight, mFontFamily, mFontAdapter))); @@ -564,9 +573,10 @@ protected Layout createLayout() { @NonNull protected Layout createLayout(final float width, final FlexMeasureMode widthMode) { FontLoader fontLoader = mFontLoaderRef.get(); - if (!mFromFontLoader && fontLoader != null && fontLoader.isFontLoaded(mFontFamily)) { + if (mFontLoadState == FontLoader.FontLoadState.FONT_UNLOAD && fontLoader != null && + fontLoader.isFontLoaded(mFontFamily)) { mDirty = true; - mFromFontLoader = true; + mFontLoadState = FontLoader.FontLoadState.FONT_LOADED; } if (mSpanned == null || mDirty) { mSpanned = createSpan(true); From 68f1af911da20a669916f2f5df2b6d0ffb2a299e Mon Sep 17 00:00:00 2001 From: arnonchen <745735906@qq.com> Date: Fri, 25 Oct 2024 14:43:49 +0800 Subject: [PATCH 13/20] fix(doc): add docs for fontUrl in JSBundle path format --- docs/api/hippy-react/modules.md | 8 ++++---- docs/api/hippy-vue/vue-native.md | 8 ++++---- docs/api/style/appearance.md | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/api/hippy-react/modules.md b/docs/api/hippy-react/modules.md index 042ac44aaf8..e274261698d 100644 --- a/docs/api/hippy-react/modules.md +++ b/docs/api/hippy-react/modules.md @@ -283,17 +283,17 @@ AsyncStorage 是一个简单的、异步的、持久化的 Key-Value 存储系 # FontLoaderModule -提供通过url动态下载远程字体的能力,下载的字体将保存在应用Cache目录下,由Hippy统一管理,可能被终端系统删除。常用字体不推荐使用该模块动态下载。 +提供通过url动态下载远程字体或者动态加载JSBundle包中字体的能力,下载的远程字体将保存在应用Cache目录下,由Hippy统一管理,可能被终端系统删除。常用字体不推荐使用该模块动态远程下载。 ## 方法 ### FontLoaderModule.load -`(fontFamily: string, fontUrl: string) => Promise` 通过fontUrl异步下载字体,下载完成后会刷新终端文本。 +`(fontFamily: string, fontUrl: string) => Promise` 通过fontUrl异步下载字体,下载完成后会刷新终端文本。 -> - fontFamily - 下载字体的字体家族,用于保存文件 -> - fontUrl - 下载字体的地址 +> - fontFamily - 下载字体的字体家族,用于保存文件和检索字体文件 +> - fontUrl - 下载字体的地址,可以是http网络地址,也可以本地文件地址或者以“hpfile://./”为前缀的JSbundle包中的相对地址 --- diff --git a/docs/api/hippy-vue/vue-native.md b/docs/api/hippy-vue/vue-native.md index de336f0ed20..7403f72dba5 100644 --- a/docs/api/hippy-vue/vue-native.md +++ b/docs/api/hippy-vue/vue-native.md @@ -292,16 +292,16 @@ console.log(Vue.Native.getElemCss(this.demon1Point)) // => { height: 80, left: 0 # FontLoaderModule -提供通过url动态下载远程字体的能力,下载的字体将保存在应用Cache目录下,由Hippy统一管理,可能被终端系统删除。常用字体不推荐使用该模块动态下载。 +提供通过url动态下载远程字体或者动态加载JSBundle包中字体的能力,下载的远程字体将保存在应用Cache目录下,由Hippy统一管理,可能被终端系统删除。常用字体不推荐使用该模块动态远程下载。 ## 方法 ### FontLoader.load -`(fontFamily: string, fontUrl: string) => Promise` 通过fontUrl异步下载字体,下载完成后会刷新终端文本。 +`(fontFamily: string, fontUrl: string) => Promise` 通过fontUrl异步下载字体,下载完成后会刷新终端文本。 -> * fontFamily - 下载字体的字体家族,用于保存文件 -> * fontUrl - 下载字体的地址 +> * fontFamily - 下载字体的字体家族,用于保存文件和检索字体文件 +> * fontUrl - 下载字体的地址,可以是http网络地址,也可以本地文件地址或者以“hpfile://./”为前缀的JSbundle包中的相对地址 --- diff --git a/docs/api/style/appearance.md b/docs/api/style/appearance.md index e99a2106658..9d7ef532021 100644 --- a/docs/api/style/appearance.md +++ b/docs/api/style/appearance.md @@ -166,7 +166,7 @@ # fontUrl -远程字体的url,会在渲染时异步动态下载 +动态加载字体的url,会在渲染时异步动态加载,可以是远程字体地址或者是使用“hpfile://./”为前缀的JSBundle包中字体路径 | 类型 | 必需 | 支持平台 | | ------------------------ | ---- | ------------ | From ca9e7c4d664fc7724f52ac069e6812207a9263ae Mon Sep 17 00:00:00 2001 From: arnonchen <745735906@qq.com> Date: Thu, 31 Oct 2024 11:00:58 +0800 Subject: [PATCH 14/20] fix(android): fix the use of weak reference in font loader --- .../renderer/component/text/FontLoader.java | 13 +++++++++---- .../tencent/renderer/node/TextVirtualNode.java | 15 +++++++++------ 2 files changed, 18 insertions(+), 10 deletions(-) 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 index d3d553a2672..51d92fab4de 100644 --- 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 @@ -136,7 +136,11 @@ public static String getFontPath(String fontFileName) { // Convert "hpfile://" to "file://" or "assets://" private String convertToLocalPathIfNeeded(String fontUrl) { if (fontUrl != null && fontUrl.startsWith("hpfile://")) { - String bundlePath = mNativeRenderRef.get().getBundlePath(); + final NativeRender nativeRender = mNativeRenderRef.get(); + String bundlePath = null; + if (nativeRender != null) { + bundlePath = mNativeRenderRef.get().getBundlePath(); + } String relativePath = fontUrl.replace("hpfile://./", ""); fontUrl = bundlePath == null ? null : bundlePath.subSequence(0, bundlePath.lastIndexOf(File.separator) + 1) @@ -230,13 +234,14 @@ public void loadAndRefresh(final String fontFamily, final String fontUrl, int ro } mConcurrentFontLoadStateMap.put(fontFamily, FontLoadState.FONT_LOADING); String convertFontUrl = convertToLocalPathIfNeeded(fontUrl); - if (mVfsManager.get() == null) { + final VfsManager vfsManager = mVfsManager.get(); + if (vfsManager == null) { if (promise != null) { promise.reject("Get vfsManager failed!"); } return; } - mVfsManager.get().fetchResourceAsync(convertFontUrl, null, null, + vfsManager.fetchResourceAsync(convertFontUrl, null, null, new FetchResourceCallback() { @Override public void onFetchCompleted(@NonNull final ResourceDataHolder dataHolder) { @@ -282,7 +287,7 @@ public void onFetchCompleted(@NonNull final ResourceDataHolder dataHolder) { saveMapFile(mUrlFontMapFile, mConcurrentUrlFontMap); saveMapFile(mLocalFontPathMapFile, mConcurrentLocalFontPathMap); TypeFaceUtil.clearFontCache(fontFamily); - NativeRender nativeRender = mNativeRenderRef.get(); + final NativeRender nativeRender = mNativeRenderRef.get(); if (nativeRender != null && needRefresh) { nativeRender.onFontLoaded(rootId); } 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 91f9ad132f5..afe4298b139 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 @@ -193,7 +193,8 @@ public void setFontFamily(String family) { if (!Objects.equals(mFontFamily, family)) { mFontFamily = family; markDirty(); - if (mFontLoaderRef.get() != null && !mFontLoaderRef.get().isFontLoaded(mFontFamily)) { + final FontLoader fontLoader = mFontLoaderRef.get(); + if (fontLoader != null && !fontLoader.isFontLoaded(mFontFamily)) { mFontLoadState = FontLoader.FontLoadState.FONT_UNLOAD; } } @@ -496,12 +497,14 @@ protected void createSpanOperationImpl(@NonNull List ops, if (mFontAdapter != null && mEnableScale) { size = (int) (size * mFontAdapter.getFontScale()); } - if (!TextUtils.isEmpty(mFontUrl) && mFontLoaderRef.get() != null) { - if (mNativeRenderRef.get() != null) { - Executor executor = mNativeRenderRef.get().getBackgroundExecutor(); + final FontLoader fontLoader = mFontLoaderRef.get(); + if (!TextUtils.isEmpty(mFontUrl) && fontLoader != null) { + final NativeRender nativeRender = mNativeRenderRef.get(); + if (nativeRender != null) { + Executor executor = nativeRender.getBackgroundExecutor(); if (executor != null) { executor.execute(() -> { - mFontLoaderRef.get().loadIfNeeded(mFontFamily, mFontUrl, getRootId()); + fontLoader.loadIfNeeded(mFontFamily, mFontUrl, getRootId()); }); } } @@ -572,7 +575,7 @@ protected Layout createLayout() { @NonNull protected Layout createLayout(final float width, final FlexMeasureMode widthMode) { - FontLoader fontLoader = mFontLoaderRef.get(); + final FontLoader fontLoader = mFontLoaderRef.get(); if (mFontLoadState == FontLoader.FontLoadState.FONT_UNLOAD && fontLoader != null && fontLoader.isFontLoaded(mFontFamily)) { mDirty = true; From c34cf0d22325ed3bceacd24fa846f287ad95e053 Mon Sep 17 00:00:00 2001 From: arnonchen <745735906@qq.com> Date: Tue, 12 Nov 2024 11:52:36 +0800 Subject: [PATCH 15/20] fix(ios, android): make the code more standardized for font loader --- docs/api/style/appearance.md | 2 +- .../src/modules/FontLoader/index.jsx | 14 +++++++------- .../ios/module/fontLoader/HippyFontLoaderModule.h | 11 ++++++++++- .../ios/module/fontLoader/HippyFontLoaderModule.mm | 13 ++++--------- modules/vfs/ios/HippyVFSDefines.h | 3 ++- .../renderer/component/text/FontLoader.java | 9 +++------ .../renderer/component/text/TypeFaceUtil.java | 3 +-- renderer/native/ios/renderer/HippyFont.mm | 3 +-- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/docs/api/style/appearance.md b/docs/api/style/appearance.md index 9d7ef532021..1e180cbb9b4 100644 --- a/docs/api/style/appearance.md +++ b/docs/api/style/appearance.md @@ -166,7 +166,7 @@ # fontUrl -动态加载字体的url,会在渲染时异步动态加载,可以是远程字体地址或者是使用“hpfile://./”为前缀的JSBundle包中字体路径 +动态加载字体的url,会在渲染时异步动态加载,可以是远程字体地址或者是使用JSBundle包中字体路径的相对地址。 | 类型 | 必需 | 支持平台 | | ------------------------ | ---- | ------------ | diff --git a/driver/js/examples/hippy-react-demo/src/modules/FontLoader/index.jsx b/driver/js/examples/hippy-react-demo/src/modules/FontLoader/index.jsx index 73eae1a9fb2..a5b00fff2b0 100644 --- a/driver/js/examples/hippy-react-demo/src/modules/FontLoader/index.jsx +++ b/driver/js/examples/hippy-react-demo/src/modules/FontLoader/index.jsx @@ -70,8 +70,8 @@ export default class LoadFontExample extends React.Component { } fillExample() { - this.setState({ inputFontFamily: 'HYHuaXianZi J' }); - this.setState({ fontUrl: 'https://zf.sc.chinaz.com/Files/DownLoad/upload/2024/1009/hanyihuaxianzijianti.ttf' }); + this.setState({ inputFontFamily: 'TencentSans W7' }); + this.setState({ fontUrl: 'https://infra-packages.openhippy.com/test/resources/TencentSans_W7.ttf' }); // this.setState({ fontUrl: 'hpfile://./assets/hanyihuaxianzijianti.ttf' } } @@ -93,13 +93,13 @@ export default class LoadFontExample extends React.Component { 通过组件fontUrl属性动态下载并使用字体 - This sentence will use font 'HYHuaXianZi F' downloaded dynamically according to 'fontUrl' property. + fontFamily='TencentSans W3' + fontUrl='https://infra-packages.openhippy.com/test/resources/TencentSans_W3.otf'> + This sentence will use font 'TencentSans W3' downloaded dynamically according to 'fontUrl' property. - 这句话将使用通过fontUrl属性下载的汉仪花仙子繁体字体. + fontFamily='TencentSans W3'> + 这句话将使用通过fontUrl属性下载的'TencentSans W3'字体. 下载并使用字体 diff --git a/framework/ios/module/fontLoader/HippyFontLoaderModule.h b/framework/ios/module/fontLoader/HippyFontLoaderModule.h index a6124758517..68f6d08c297 100644 --- a/framework/ios/module/fontLoader/HippyFontLoaderModule.h +++ b/framework/ios/module/fontLoader/HippyFontLoaderModule.h @@ -28,11 +28,19 @@ NS_ASSUME_NONNULL_BEGIN HIPPY_EXTERN NSString *const HippyLoadFontNotification; typedef NS_ENUM(NSInteger, HippyFontUrlState) { + HippyFontUrlPending = 0, HippyFontUrlLoading = 1, HippyFontUrlLoaded = 2, HippyFontUrlFailed = 3, }; +/** + * @class HippyFontLoaderModule + * @brief This class is responsible for loading and registering fonts. + * + * This class is a hippy module, providing a load method for the front end to download and register fonts. + * It also provides method for native side to register font. + */ @interface HippyFontLoaderModule : NSObject /** @@ -72,7 +80,8 @@ typedef NS_ENUM(NSInteger, HippyFontUrlState) { * @param resolve - The callback block for downloading successful. * @param reject - The callback block for downloading failed. */ -- (void)load:(NSString *)fontFamily from:(NSString *)urlString resolver:(nullable HippyPromiseResolveBlock)resolve rejecter:(nullable HippyPromiseRejectBlock)reject; +- (void)load:(NSString *)fontFamily from:(NSString *)urlString resolver:(nullable HippyPromiseResolveBlock)resolve + rejecter:(nullable HippyPromiseRejectBlock)reject; @end diff --git a/framework/ios/module/fontLoader/HippyFontLoaderModule.mm b/framework/ios/module/fontLoader/HippyFontLoaderModule.mm index 7bbb47b7fc4..64debb826dc 100644 --- a/framework/ios/module/fontLoader/HippyFontLoaderModule.mm +++ b/framework/ios/module/fontLoader/HippyFontLoaderModule.mm @@ -135,8 +135,7 @@ + (BOOL)registerFontIfNeeded:(NSString *)fontFamily { [fontRegistered addObject:fontFile]; isFontRegistered = YES; HippyLogInfo(@"register font \"%@\" success!", fontFile); - } - else { + } else { if (error.domain == kFontLoaderModuleErrorDomain && error.code == FontLoaderErrorRegisterError) { [fileNotExist addObject:fontFile]; } @@ -180,8 +179,7 @@ - (void)saveFontfamily:(NSString *)fontFamily url:(NSString *)url filePath:(NSSt if (!fontFiles) { fontFiles = [NSMutableArray arrayWithObject:filePath]; [fontFamilyToFiles setObject:fontFiles forKey:fontFamily]; - } - else { + } else { [fontFiles addObject:filePath]; } [urlToFilePath writeToFile:fontUrlSavePath atomically:YES]; @@ -236,9 +234,7 @@ - (void)saveFontfamily:(NSString *)fontFamily url:(NSString *)url filePath:(NSSt resolve([NSString stringWithFormat:@"load local font file \"%@\" success!", fontFilePath]); } [HippyFontLoaderModule setUrl:urlString state:HippyFontUrlLoaded]; - } - // is http url - else { + } else { // is http url NSFileManager *fileManager = [NSFileManager defaultManager]; if (![fileManager fileExistsAtPath:fontDirPath]) { NSError *error; @@ -263,8 +259,7 @@ - (void)saveFontfamily:(NSString *)fontFamily url:(NSString *)url filePath:(NSSt resolve([NSString stringWithFormat:@"download font file \"%@\" success!", fileName]); } [HippyFontLoaderModule setUrl:urlString state:HippyFontUrlLoaded]; - } - else { + } else { if (reject) { NSString *errorKey = [NSString stringWithFormat:@"%lu", FontLoaderErrorWriteFileError]; reject(errorKey, @"font request error", error); diff --git a/modules/vfs/ios/HippyVFSDefines.h b/modules/vfs/ios/HippyVFSDefines.h index b281b9c8520..04f6092c581 100644 --- a/modules/vfs/ios/HippyVFSDefines.h +++ b/modules/vfs/ios/HippyVFSDefines.h @@ -40,7 +40,8 @@ enum HippyVFSRscType { }; enum HippyVFSURLType { - HippyVFSURLTypeHTTP = 1, + HippyVFSURLTypeUnknown = 0, + HippyVFSURLTypeHTTP, HippyVFSURLTypeFile, }; 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 index 51d92fab4de..2b78dd86f74 100644 --- 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 @@ -261,21 +261,18 @@ public void onFetchCompleted(@NonNull final ResourceDataHolder dataHolder) { String fileName = fontFamily + getFileExtension(fontUrl); if (convertFontUrl.equals(mConcurrentLocalFontPathMap.get(fileName))) { needRefresh = false; - } - else { + } else { mConcurrentLocalFontPathMap.put(fileName, convertFontUrl); } if (promise != null) { promise.resolve(String.format("Load local font %s success", convertFontUrl)); } - } - else { + } else { String fileName = fontFamily + getFileExtension(fontUrl); if (!saveFontFile(bytes, fileName, promise)) { mConcurrentFontLoadStateMap.remove(fontFamily); return; - } - else { + } else { File fontFile = new File(mFontDir, fileName); mConcurrentUrlFontMap.put(fontUrl, fontFile.getAbsolutePath()); if (promise != null) { 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 f4a00a65af9..b3cb3df5d2a 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 @@ -113,8 +113,7 @@ private static Typeface createExactTypeFace(String fileName) { } catch (Exception e) { LogUtils.w(TAG, e.getMessage()); } - } - else { + } else { if (bundleFontPath.startsWith(UrlUtils.PREFIX_FILE)) { bundleFontPath = bundleFontPath.substring(UrlUtils.PREFIX_FILE.length()); } diff --git a/renderer/native/ios/renderer/HippyFont.mm b/renderer/native/ios/renderer/HippyFont.mm index b5880732bcc..d876c8a49be 100644 --- a/renderer/native/ios/renderer/HippyFont.mm +++ b/renderer/native/ios/renderer/HippyFont.mm @@ -287,8 +287,7 @@ + (UIFont *)updateFont:(UIFont *)font font = [UIFont fontWithName:familyName size:fontSize]; if (font) { didFindFont = YES; - } - else { + } else { fontWeight = weight ? fontWeight : weightOfFont(font); isItalic = style ? isItalic : isItalicFont(font); isCondensed = isCondensedFont(font); From 5b38b3f6fa5715c3c2d83a1d404632471e0c4ee3 Mon Sep 17 00:00:00 2001 From: arnonchen <745735906@qq.com> Date: Tue, 12 Nov 2024 14:38:15 +0800 Subject: [PATCH 16/20] fix(ios): fix HippyFontLoaderTest using local file --- hippy.podspec | 1 + tests/ios/HippyFontLoaderTest.m | 13 ++++--------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/hippy.podspec b/hippy.podspec index ef43c7e17ff..7ef1bc1b2e8 100644 --- a/hippy.podspec +++ b/hippy.podspec @@ -365,6 +365,7 @@ Pod::Spec.new do |s| s.test_spec 'UnitTests' do |test_spec| test_spec.source_files = 'tests/ios/**/*.{h,m,mm}' + test_spec.resources = 'framework/examples/ios-demo/fonts/TTTGB-Medium.otf' test_spec.dependency 'OCMock' end diff --git a/tests/ios/HippyFontLoaderTest.m b/tests/ios/HippyFontLoaderTest.m index 6662c7473b8..51c23285fb7 100644 --- a/tests/ios/HippyFontLoaderTest.m +++ b/tests/ios/HippyFontLoaderTest.m @@ -44,8 +44,10 @@ - (void)tearDown { - (void)testHippyFontLoaderModule { NSString* invalidURL = @"https://example.url"; // set arbitrary valid font file url - NSString* validURL = @"https://zf.sc.chinaz.com/Files/DownLoad/upload/2024/1009/hanyihuaxianzijianti.ttf"; - NSString* fontFamily = @"HYHuaXianZi J"; + NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; + NSString* filePath = [testBundle pathForResource:@"TTTGB-Medium" ofType:@"otf"]; + NSString* validURL = [@"file://" stringByAppendingString:filePath]; + NSString* fontFamily = @"TTTGB Medium"; HippyBridge *bridge = [[HippyBridge alloc] initWithDelegate:nil moduleProvider:nil launchOptions:nil executorKey:nil]; HippyFontLoaderModule *fontLoader = [[HippyFontLoaderModule alloc] init]; [fontLoader setValue:bridge forKey:@"bridge"]; @@ -109,11 +111,4 @@ - (void)testHippyFontLoaderModule { [[NSFileManager defaultManager] removeItemAtPath:[fontPath stringByDeletingLastPathComponent] error:nil]; } -- (void)testPerformanceExample { - // This is an example of a performance test case. - [self measureBlock:^{ - // Put the code you want to measure the time of here. - }]; -} - @end From 4b9c093a9bb0a9e98b89e7a8d7d52214410ad531 Mon Sep 17 00:00:00 2001 From: arnonchen <745735906@qq.com> Date: Wed, 11 Dec 2024 10:45:49 +0800 Subject: [PATCH 17/20] fix(ios): improve code after discussion --- .../module/fontLoader/HippyFontLoaderModule.h | 11 +- .../fontLoader/HippyFontLoaderModule.mm | 110 +++++++++++------- renderer/native/ios/renderer/HippyFont.mm | 22 ++-- 3 files changed, 85 insertions(+), 58 deletions(-) diff --git a/framework/ios/module/fontLoader/HippyFontLoaderModule.h b/framework/ios/module/fontLoader/HippyFontLoaderModule.h index 68f6d08c297..9750caea4c4 100644 --- a/framework/ios/module/fontLoader/HippyFontLoaderModule.h +++ b/framework/ios/module/fontLoader/HippyFontLoaderModule.h @@ -25,8 +25,6 @@ NS_ASSUME_NONNULL_BEGIN -HIPPY_EXTERN NSString *const HippyLoadFontNotification; - typedef NS_ENUM(NSInteger, HippyFontUrlState) { HippyFontUrlPending = 0, HippyFontUrlLoading = 1, @@ -58,6 +56,15 @@ typedef NS_ENUM(NSInteger, HippyFontUrlState) { */ + (BOOL)registerFontIfNeeded:(NSString *)fontFamily; +/** + * If the font in the url has not been downloaded, download the font. + * Function will be called when downloading fonts through url property of text component. + * + * @param fontFamily - The font family needs to be downloaded + * @param url - The font url needs to download from. + */ ++ (void)loadFontIfNeeded:(NSString *)fontFamily fromUrl:(NSString *)url; + /** * Whether the font is downloading from the url. * diff --git a/framework/ios/module/fontLoader/HippyFontLoaderModule.mm b/framework/ios/module/fontLoader/HippyFontLoaderModule.mm index 64debb826dc..1b96f59405f 100644 --- a/framework/ios/module/fontLoader/HippyFontLoaderModule.mm +++ b/framework/ios/module/fontLoader/HippyFontLoaderModule.mm @@ -31,25 +31,27 @@ #import "HippyUIManager.h" -NSString *const HippyLoadFontNotification = @"HippyLoadFontNotification"; +static NSString *const HippyLoadFontNotification = @"HippyLoadFontNotification"; +static NSString *const HippyLoadFontUrlKey = @"fontUrl"; +static NSString *const HippyLoadFontFamilyKey = @"fontFamily"; 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; static NSUInteger const FontLoaderErrorWriteFileError = 4; -NSString *const HippyFontDirName = @"HippyFonts"; -NSString *const HippyFontUrlCacheName = @"urlToFilePath.plist"; -NSString *const HippyFontFamilyCacheName = @"fontFaimilyToFiles.plist"; +static NSString *const HippyFontDirName = @"HippyFonts"; +static NSString *const HippyFontUrlCacheName = @"urlToFilePath.plist"; +static NSString *const HippyFontFamilyCacheName = @"fontFaimilyToFiles.plist"; -static dispatch_queue_t serialQueue; -static NSMutableDictionary *urlToFilePath; -static NSMutableDictionary *fontFamilyToFiles; -static NSMutableDictionary *urlLoadState = [NSMutableDictionary dictionary]; -static NSMutableArray *fontRegistered = [NSMutableArray array]; -static NSString *fontDirPath; -static NSString *fontUrlSavePath; -static NSString *fontFamilySavePath; +static dispatch_queue_t gSerialQueue; +static NSMutableDictionary *gUrlToFilePath; +static NSMutableDictionary *gFontFamilyToFiles; +static NSMutableDictionary *gUrlLoadState; +static NSMutableArray *gFontRegistered; +static NSString *gFontDirPath; +static NSString *gFontUrlSavePath; +static NSString *gFontFamilySavePath; @implementation HippyFontLoaderModule @@ -67,51 +69,70 @@ - (instancetype)init { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); NSString *cachesDirectory = [paths objectAtIndex:0]; - fontDirPath = [cachesDirectory stringByAppendingPathComponent:HippyFontDirName]; - fontUrlSavePath = [fontDirPath stringByAppendingPathComponent:HippyFontUrlCacheName]; - fontFamilySavePath = [fontDirPath stringByAppendingPathComponent:HippyFontFamilyCacheName]; - serialQueue = dispatch_queue_create("com.tencent.hippy.FontLoaderQueue", DISPATCH_QUEUE_SERIAL); + gFontDirPath = [cachesDirectory stringByAppendingPathComponent:HippyFontDirName]; + gFontUrlSavePath = [gFontDirPath stringByAppendingPathComponent:HippyFontUrlCacheName]; + gFontFamilySavePath = [gFontDirPath stringByAppendingPathComponent:HippyFontFamilyCacheName]; + gSerialQueue = dispatch_queue_create("com.tencent.hippy.FontLoaderQueue", DISPATCH_QUEUE_SERIAL); }); } return self; } + (dispatch_queue_t)getFontSerialQueue { - return serialQueue; + return gSerialQueue; } + (void)setUrl:(NSString *)url state:(HippyFontUrlState)state { - [urlLoadState setObject:@(state) forKey:url]; + if (!gUrlLoadState) { + gUrlLoadState = [NSMutableDictionary dictionary]; + } + [gUrlLoadState setObject:@(state) forKey:url]; } + (BOOL)isUrlLoading:(NSString *)url { - return [[urlLoadState objectForKey:url] integerValue] == HippyFontUrlLoading; + if (!gUrlLoadState) { + gUrlLoadState = [NSMutableDictionary dictionary]; + } + return [[gUrlLoadState objectForKey:url] integerValue] == HippyFontUrlLoading; } +// Read file to init dict if needed. This function will be called asynchronously. + (void)initDictIfNeeded { - if (fontFamilyToFiles == nil) { - fontFamilyToFiles = [NSMutableDictionary dictionaryWithContentsOfFile:fontFamilySavePath]; - if (fontFamilyToFiles == nil) { - fontFamilyToFiles = [NSMutableDictionary dictionary]; + if (gFontFamilyToFiles == nil) { + gFontFamilyToFiles = [NSMutableDictionary dictionaryWithContentsOfFile:gFontFamilySavePath]; + if (gFontFamilyToFiles == nil) { + gFontFamilyToFiles = [NSMutableDictionary dictionary]; } } - if (urlToFilePath == nil) { - urlToFilePath = [NSMutableDictionary dictionaryWithContentsOfFile:fontUrlSavePath]; - if (urlToFilePath == nil) { - urlToFilePath = [NSMutableDictionary dictionary]; + if (gUrlToFilePath == nil) { + gUrlToFilePath = [NSMutableDictionary dictionaryWithContentsOfFile:gFontUrlSavePath]; + if (gUrlToFilePath == nil) { + gUrlToFilePath = [NSMutableDictionary dictionary]; } } } ++ (void)loadFontIfNeeded:(NSString *)fontFamily fromUrl:(NSString *)url { + if (url && ![HippyFontLoaderModule isUrlLoading:url]) { + dispatch_async([HippyFontLoaderModule getFontSerialQueue], ^{ + NSString *fontPath = [HippyFontLoaderModule getFontPath:url]; + if (!fontPath && fontFamily) { + NSDictionary *userInfo = @{HippyLoadFontUrlKey: url, HippyLoadFontFamilyKey: fontFamily}; + [[NSNotificationCenter defaultCenter] postNotificationName:HippyLoadFontNotification object:nil userInfo:userInfo]; + } + }); + } +} + - (void)loadFont:(NSNotification *)notification { - NSString *urlString = [notification.userInfo objectForKey:@"fontUrl"]; - NSString *fontFamily = [notification.userInfo objectForKey:@"fontFamily"]; + NSString *urlString = [notification.userInfo objectForKey:HippyLoadFontUrlKey]; + NSString *fontFamily = [notification.userInfo objectForKey:HippyLoadFontFamilyKey]; [self load:fontFamily from:urlString resolver:nil rejecter:nil]; } + (NSString *)getFontPath:(NSString *)url { [self initDictIfNeeded]; - NSString *fontFilePath = urlToFilePath[url]; + NSString *fontFilePath = gUrlToFilePath[url]; if (!fontFilePath) { return nil; } @@ -124,15 +145,18 @@ + (NSString *)getFontPath:(NSString *)url { + (BOOL)registerFontIfNeeded:(NSString *)fontFamily { [self initDictIfNeeded]; - NSMutableArray *fontFiles = [fontFamilyToFiles objectForKey:fontFamily]; + NSMutableArray *fontFiles = [gFontFamilyToFiles objectForKey:fontFamily]; + if (!gFontRegistered) { + gFontRegistered = [NSMutableArray array]; + } BOOL isFontRegistered = NO; if (fontFiles) { NSMutableArray *fileNotExist = [NSMutableArray array]; for (NSString *fontFile in fontFiles) { - if (![fontRegistered containsObject:fontFile]) { + if (![gFontRegistered containsObject:fontFile]) { NSError *error = nil; if ([self registerFontFromURL:fontFile error:&error]) { - [fontRegistered addObject:fontFile]; + [gFontRegistered addObject:fontFile]; isFontRegistered = YES; HippyLogInfo(@"register font \"%@\" success!", fontFile); } else { @@ -174,16 +198,16 @@ + (BOOL)registerFontFromURL:(NSString *)urlString error:(NSError **)error { - (void)saveFontfamily:(NSString *)fontFamily url:(NSString *)url filePath:(NSString *)filePath { [HippyFontLoaderModule initDictIfNeeded]; - [urlToFilePath setObject:filePath forKey:url]; - NSMutableArray *fontFiles = [fontFamilyToFiles objectForKey:fontFamily]; + [gUrlToFilePath setObject:filePath forKey:url]; + NSMutableArray *fontFiles = [gFontFamilyToFiles objectForKey:fontFamily]; if (!fontFiles) { fontFiles = [NSMutableArray arrayWithObject:filePath]; - [fontFamilyToFiles setObject:fontFiles forKey:fontFamily]; + [gFontFamilyToFiles setObject:fontFiles forKey:fontFamily]; } else { [fontFiles addObject:filePath]; } - [urlToFilePath writeToFile:fontUrlSavePath atomically:YES]; - [fontFamilyToFiles writeToFile:fontFamilySavePath atomically:YES]; + [gUrlToFilePath writeToFile:gFontUrlSavePath atomically:YES]; + [gFontFamilyToFiles writeToFile:gFontFamilySavePath atomically:YES]; } @@ -233,12 +257,14 @@ - (void)saveFontfamily:(NSString *)fontFamily url:(NSString *)url filePath:(NSSt if (resolve) { resolve([NSString stringWithFormat:@"load local font file \"%@\" success!", fontFilePath]); } - [HippyFontLoaderModule setUrl:urlString state:HippyFontUrlLoaded]; + @synchronized (strongSelf) { + [HippyFontLoaderModule setUrl:urlString state:HippyFontUrlLoaded]; + } } else { // is http url NSFileManager *fileManager = [NSFileManager defaultManager]; - if (![fileManager fileExistsAtPath:fontDirPath]) { + if (![fileManager fileExistsAtPath:gFontDirPath]) { NSError *error; - [fileManager createDirectoryAtPath:fontDirPath withIntermediateDirectories:YES attributes:nil error:&error]; + [fileManager createDirectoryAtPath:gFontDirPath withIntermediateDirectories:YES attributes:nil error:&error]; if (error) { NSString *errorKey = [NSString stringWithFormat:@"%lu", FontLoaderErrorDirectoryError]; if (reject) { @@ -249,7 +275,7 @@ - (void)saveFontfamily:(NSString *)fontFamily url:(NSString *)url filePath:(NSSt } } NSString *fileName = [fontFamily stringByAppendingFormat:@".%@", [response.suggestedFilename pathExtension]]; - NSString *fontFilePath = [fontDirPath stringByAppendingPathComponent:fileName]; + NSString *fontFilePath = [gFontDirPath stringByAppendingPathComponent:fileName]; if ([data writeToFile:fontFilePath atomically:YES]) { dispatch_async([HippyFontLoaderModule getFontSerialQueue], ^{ [strongSelf saveFontfamily:fontFamily url:urlString filePath:fontFilePath]; diff --git a/renderer/native/ios/renderer/HippyFont.mm b/renderer/native/ios/renderer/HippyFont.mm index d876c8a49be..829b6d9c7a4 100644 --- a/renderer/native/ios/renderer/HippyFont.mm +++ b/renderer/native/ios/renderer/HippyFont.mm @@ -206,17 +206,11 @@ + (UIFont *)updateFont:(UIFont *)font variant:(NSArray *)variant scaleMultiplier:(CGFloat)scaleMultiplier { // Defaults - if (url && ![HippyFontLoaderModule isUrlLoading:url]) { + if (url) { dispatch_async([HippyFontLoaderModule getFontSerialQueue], ^{ - NSString *fontPath = [HippyFontLoaderModule getFontPath:url]; - if (!fontPath && family) { - NSDictionary *userInfo = @{@"fontUrl": url, @"fontFamily": family}; - [[NSNotificationCenter defaultCenter] postNotificationName:HippyLoadFontNotification object:nil userInfo:userInfo]; - } + [HippyFontLoaderModule loadFontIfNeeded:family fromUrl:url]; }); } - - // Defaults static NSString *defaultFontFamily; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ @@ -252,12 +246,6 @@ + (UIFont *)updateFont:(UIFont *)font fontWeight = weight ? [HippyConvert NativeRenderFontWeight:weight] : fontWeight; BOOL didFindFont = NO; - - if (fontNamesForFamilyName(familyName).count == 0) { - dispatch_async([HippyFontLoaderModule getFontSerialQueue], ^{ - [HippyFontLoaderModule registerFontIfNeeded:familyName]; - }); - } // Handle system font as special case. This ensures that we preserve // the specific metrics of the standard system font as closely as possible. @@ -280,6 +268,12 @@ + (UIFont *)updateFont:(UIFont *)font } } } + + if (!didFindFont && fontNamesForFamilyName(familyName).count == 0) { + dispatch_async([HippyFontLoaderModule getFontSerialQueue], ^{ + [HippyFontLoaderModule registerFontIfNeeded:familyName]; + }); + } // Gracefully handle being given a font name rather than font family, for // example: "Helvetica Light Oblique" rather than just "Helvetica". From e20859d206fe462c5a433ff770eed4086b039791 Mon Sep 17 00:00:00 2001 From: arnonchen <745735906@qq.com> Date: Wed, 11 Dec 2024 10:56:45 +0800 Subject: [PATCH 18/20] fix(ios): change vfs load function in fontLoader --- .../ios/module/fontLoader/HippyFontLoaderModule.mm | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/framework/ios/module/fontLoader/HippyFontLoaderModule.mm b/framework/ios/module/fontLoader/HippyFontLoaderModule.mm index 1b96f59405f..0cead5585b0 100644 --- a/framework/ios/module/fontLoader/HippyFontLoaderModule.mm +++ b/framework/ios/module/fontLoader/HippyFontLoaderModule.mm @@ -231,13 +231,11 @@ - (void)saveFontfamily:(NSString *)fontFamily url:(NSString *)url filePath:(NSSt } __weak __typeof(self) weakSelf = self; - [self.bridge loadContentsAsynchronouslyFromUrl:urlString - method:@"Get" - params:nil - body:nil - queue:nil - progress:nil - completionHandler:^(NSData *data, NSDictionary *userInfo, NSURLResponse *response, NSError *error) { + [self.bridge loadContentsAsyncFromUrl:urlString + params:nil + queue:nil + progress:nil + completionHandler:^(NSData *data, NSDictionary *userInfo, NSURLResponse *response, NSError *error) { __strong __typeof(weakSelf) strongSelf = weakSelf; if (error) { if (reject) { From 6cabb6070ff6696b01566b577cb17eb98b0ba7f4 Mon Sep 17 00:00:00 2001 From: wwwcg Date: Mon, 16 Dec 2024 20:36:47 +0800 Subject: [PATCH 19/20] fix(ios): update hippy.podspec due to code coverage failure --- hippy.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hippy.podspec b/hippy.podspec index 7ef1bc1b2e8..c4bcdd27ba3 100644 --- a/hippy.podspec +++ b/hippy.podspec @@ -365,7 +365,7 @@ Pod::Spec.new do |s| s.test_spec 'UnitTests' do |test_spec| test_spec.source_files = 'tests/ios/**/*.{h,m,mm}' - test_spec.resources = 'framework/examples/ios-demo/fonts/TTTGB-Medium.otf' + test_spec.resource_bundles = { 'TestFonts' => ['framework/examples/ios-demo/fonts/TTTGB-Medium.otf'], } test_spec.dependency 'OCMock' end From f69ad773aefc3933bb396e33ddbd4efd826ddd40 Mon Sep 17 00:00:00 2001 From: wwwcg Date: Mon, 16 Dec 2024 20:38:08 +0800 Subject: [PATCH 20/20] Update HippyFontLoaderTest.m --- tests/ios/HippyFontLoaderTest.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ios/HippyFontLoaderTest.m b/tests/ios/HippyFontLoaderTest.m index 51c23285fb7..6cd19337441 100644 --- a/tests/ios/HippyFontLoaderTest.m +++ b/tests/ios/HippyFontLoaderTest.m @@ -45,7 +45,7 @@ - (void)testHippyFontLoaderModule { NSString* invalidURL = @"https://example.url"; // set arbitrary valid font file url NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; - NSString* filePath = [testBundle pathForResource:@"TTTGB-Medium" ofType:@"otf"]; + NSString* filePath = [testBundle pathForResource:@"TestFonts.bundle/TTTGB-Medium" ofType:@"otf"]; NSString* validURL = [@"file://" stringByAppendingString:filePath]; NSString* fontFamily = @"TTTGB Medium"; HippyBridge *bridge = [[HippyBridge alloc] initWithDelegate:nil moduleProvider:nil launchOptions:nil executorKey:nil];