Add fonts support

This commit is contained in:
amsyarasyiq 2024-02-25 09:28:44 +08:00
parent 1d280d78da
commit 4dc16d0469
3 changed files with 157 additions and 3 deletions

View file

@ -13,7 +13,7 @@ android {
minSdk = 24
targetSdk = 33
versionCode = 1
versionName = "0.1.0"
versionName = "0.1.1"
}
buildTypes {

View file

@ -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<String>,
style: Int,
assetManager: AssetManager
): Typeface? {
val fontFamilies: MutableList<FontFamily> = 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)
}
}

View file

@ -34,7 +34,8 @@ data class LoaderConfig(
class Main : IXposedHookLoadPackage {
private val pyonModules: Array<PyonModule> = 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])