From 4dc16d0469b00a80b419496a4ba8701faf8d9abb Mon Sep 17 00:00:00 2001 From: amsyarasyiq <82711525+amsyarasyiq@users.noreply.github.com> Date: Sun, 25 Feb 2024 09:28:44 +0800 Subject: [PATCH] Add fonts support --- app/build.gradle.kts | 2 +- .../kotlin/com/pyoncord/xposed/FontsModule.kt | 153 ++++++++++++++++++ .../main/kotlin/com/pyoncord/xposed/Main.kt | 5 +- 3 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 app/src/main/kotlin/com/pyoncord/xposed/FontsModule.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 972489d..40b0c3e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -13,7 +13,7 @@ android { minSdk = 24 targetSdk = 33 versionCode = 1 - versionName = "0.1.0" + versionName = "0.1.1" } buildTypes { diff --git a/app/src/main/kotlin/com/pyoncord/xposed/FontsModule.kt b/app/src/main/kotlin/com/pyoncord/xposed/FontsModule.kt new file mode 100644 index 0000000..2da1d01 --- /dev/null +++ b/app/src/main/kotlin/com/pyoncord/xposed/FontsModule.kt @@ -0,0 +1,153 @@ +// credits to janisslsm from his PR: https://github.com/vendetta-mod/VendettaXposed/pull/17 +// the functions hooked are based on the RN codebase, just modified to add fonts + +package com.pyoncord.xposed + +import android.content.res.AssetManager +import android.os.Build +import android.graphics.Color +import android.graphics.Typeface +import android.graphics.Typeface.CustomFallbackBuilder +import android.graphics.fonts.Font +import android.graphics.fonts.FontFamily +import android.webkit.URLUtil +import de.robv.android.xposed.XC_MethodReplacement +import de.robv.android.xposed.XposedBridge +import de.robv.android.xposed.XposedHelpers +import de.robv.android.xposed.callbacks.XC_LoadPackage +import kotlinx.serialization.json.* +import java.io.IOException +import java.io.File + +class FontsModule: PyonModule() { + private val EXTENSIONS = arrayOf("", "_bold", "_italic", "_bold_italic") + private val FILE_EXTENSIONS = arrayOf(".ttf", ".otf") + private val FONTS_ASSET_PATH = "fonts/" + private lateinit var fontsDir: File + private lateinit var fontsAbsPath: String + + override fun buildJson(builder: JsonObjectBuilder) { + builder.apply { + put("supportFonts", true) + } + } + + override fun onInit(packageParam: XC_LoadPackage.LoadPackageParam) = with (packageParam) { + fontsDir = File(appInfo.dataDir, "files/pyoncord/fonts").also { it.mkdirs() } + fontsAbsPath = fontsDir.absolutePath + "/" + + XposedHelpers.findAndHookMethod("com.facebook.react.views.text.ReactFontManager", classLoader, "createAssetTypeface", + String::class.java, + Int::class.java, + "android.content.res.AssetManager", object : XC_MethodReplacement() { + override fun replaceHookedMethod(param: MethodHookParam): Typeface? { + val fontFamilyName: String = param.args[0].toString(); + val style: Int = param.args[1] as Int; + val assetManager: AssetManager = param.args[2] as AssetManager; + return createAssetTypeface(fontFamilyName, style, assetManager) + } + }); + + return@with + } + + private fun createAssetTypefaceWithFallbacks( + fontFamilyNames: Array, + style: Int, + assetManager: AssetManager + ): Typeface? { + val fontFamilies: MutableList = ArrayList() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // Iterate over the list of fontFamilyNames, constructing new FontFamily objects + // for use in the CustomFallbackBuilder below. + for (fontFamilyName in fontFamilyNames) { + for (fontRootPath in arrayOf(fontsAbsPath, FONTS_ASSET_PATH)) { + for (fileExtension in FILE_EXTENSIONS) { + val fileName = java.lang.StringBuilder() + .append(fontRootPath) + .append(fontFamilyName) + .append(fileExtension) + .toString() + try { + val builder = if (fileName[0] == '/') Font.Builder(File(fileName)) else Font.Builder(assetManager, fileName) + val font = builder.build() + val family = FontFamily.Builder(font).build() + fontFamilies.add(family) + } catch (e: java.lang.RuntimeException) { + // If the typeface asset does not exist, try another extension. + continue + } catch (e: IOException) { + // If the font asset does not exist, try another extension. + continue + } + } + } + } + + // If there's some problem constructing fonts, fall back to the default behavior. + if (fontFamilies.size == 0) { + return createAssetTypeface(fontFamilyNames[0], style, assetManager) + } + val fallbackBuilder = CustomFallbackBuilder(fontFamilies[0]) + for (i in 1 until fontFamilies.size) { + fallbackBuilder.addCustomFallback(fontFamilies[i]) + } + return fallbackBuilder.build() + } + return null + } + + private fun createAssetTypeface( + fontFamilyName_: String, style: Int, assetManager: AssetManager + ): Typeface? { + // This logic attempts to safely check if the frontend code is attempting to use + // fallback fonts, and if it is, to use the fallback typeface creation logic. + var fontFamilyName: String = fontFamilyName_ + val fontFamilyNames = + fontFamilyName.split(",".toRegex()) + .dropLastWhile { it.isEmpty() } + .toTypedArray() + for (i in fontFamilyNames.indices) { + fontFamilyNames[i] = fontFamilyNames[i].trim() + } + + // If there are multiple font family names: + // For newer versions of Android, construct a Typeface with fallbacks + // For older versions of Android, ignore all the fallbacks and just use the first font family + if (fontFamilyNames.size > 1) { + fontFamilyName = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return createAssetTypefaceWithFallbacks(fontFamilyNames, style, assetManager) + } else { + fontFamilyNames[0] + } + } + + val extension = EXTENSIONS[style] + + // Lastly, after all those checks above, this is the original RN logic for + // getting the typeface. + for (fontRootPath in arrayOf(fontsAbsPath, FONTS_ASSET_PATH)) { + for (fileExtension in FILE_EXTENSIONS) { + val fileName = java.lang.StringBuilder() + .append(fontRootPath) + .append(fontFamilyName) + .append(extension) + .append(fileExtension) + .toString() + + return try { + if (fileName[0] == '/') + Typeface.createFromFile(fileName) + else + Typeface.createFromAsset(assetManager, fileName) + } catch (e: java.lang.RuntimeException) { + // If the typeface asset does not exist, try another extension. + XposedBridge.log("(under) acquiring $fileName font failed ${e.cause}") + continue + } + } + } + + return Typeface.create(fontFamilyName, style) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pyoncord/xposed/Main.kt b/app/src/main/kotlin/com/pyoncord/xposed/Main.kt index a51298e..9228e23 100644 --- a/app/src/main/kotlin/com/pyoncord/xposed/Main.kt +++ b/app/src/main/kotlin/com/pyoncord/xposed/Main.kt @@ -34,7 +34,8 @@ data class LoaderConfig( class Main : IXposedHookLoadPackage { private val pyonModules: Array = arrayOf( ThemeModule(), - SysColorsModule() + SysColorsModule(), + FontsModule() ) fun buildLoaderJsonString(): String { @@ -152,7 +153,7 @@ class Main : IXposedHookLoadPackage { ) } - XposedBridge.invokeOriginalMethod( + if (bundle.exists()) XposedBridge.invokeOriginalMethod( loadScriptFromFile, param.thisObject, arrayOf(bundle.absolutePath, bundle.absolutePath, param.args[2])