Update FontsModule.kt
This commit is contained in:
parent
57ebd3daed
commit
ee7e30e67c
1 changed files with 90 additions and 108 deletions
|
|
@ -1,5 +1,3 @@
|
||||||
@file:Suppress("DEPRECATION")
|
|
||||||
|
|
||||||
package GoonXposed.xposed.modules.appearance
|
package GoonXposed.xposed.modules.appearance
|
||||||
|
|
||||||
import android.content.res.AssetManager
|
import android.content.res.AssetManager
|
||||||
|
|
@ -15,8 +13,6 @@ import GoonXposed.xposed.Constants
|
||||||
import GoonXposed.xposed.Module
|
import GoonXposed.xposed.Module
|
||||||
import GoonXposed.xposed.Utils.Companion.JSON
|
import GoonXposed.xposed.Utils.Companion.JSON
|
||||||
import GoonXposed.xposed.Utils.Log
|
import GoonXposed.xposed.Utils.Log
|
||||||
import GoonXposed.xposed.asDir
|
|
||||||
import GoonXposed.xposed.asFile
|
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.*
|
||||||
import io.ktor.client.engine.cio.*
|
import io.ktor.client.engine.cio.*
|
||||||
|
|
@ -45,92 +41,77 @@ object FontsModule : Module() {
|
||||||
private const val FONTS_ASSET_PATH = "fonts/"
|
private const val FONTS_ASSET_PATH = "fonts/"
|
||||||
|
|
||||||
private lateinit var fontsDir: File
|
private lateinit var fontsDir: File
|
||||||
private var fontsDownloadsDir: File? = null
|
private lateinit var fontsDownloadsDir: File
|
||||||
private var fontsAbsPath: String? = null
|
private var fontsAbsPath: String? = null
|
||||||
|
|
||||||
override fun buildPayload(builder: JsonObjectBuilder) {
|
override fun buildPayload(builder: JsonObjectBuilder) {
|
||||||
builder.put("fontPatch", 2)
|
builder.apply {
|
||||||
|
put("fontPatch", 2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
|
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
|
||||||
// ReactFontManager hijack runs regardless of fonts.json presence; it falls back to the
|
XposedHelpers.findAndHookMethod(
|
||||||
// default Typeface chain if no custom font file is found.
|
|
||||||
listOf(
|
|
||||||
"com.facebook.react.common.assets.ReactFontManager\$Companion",
|
"com.facebook.react.common.assets.ReactFontManager\$Companion",
|
||||||
"com.facebook.react.views.text.ReactFontManager\$Companion",
|
classLoader,
|
||||||
).forEach { clsName ->
|
"createAssetTypeface",
|
||||||
XposedHelpers.findClassIfExists(clsName, classLoader)?.let { cls ->
|
String::class.java,
|
||||||
runCatching {
|
Int::class.java,
|
||||||
XposedHelpers.findAndHookMethod(
|
"android.content.res.AssetManager",
|
||||||
cls,
|
object : XC_MethodReplacement() {
|
||||||
"createAssetTypeface",
|
override fun replaceHookedMethod(param: MethodHookParam): Typeface? {
|
||||||
String::class.java,
|
val fontFamilyName: String = param.args[0].toString()
|
||||||
Int::class.java,
|
val style: Int = param.args[1] as Int
|
||||||
AssetManager::class.java,
|
val assetManager: AssetManager = param.args[2] as AssetManager
|
||||||
object : XC_MethodReplacement() {
|
return createAssetTypeface(fontFamilyName, style, assetManager)
|
||||||
override fun replaceHookedMethod(param: MethodHookParam): Any? {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}.onFailure { e ->
|
|
||||||
Log.e("Failed to hook ReactFontManager: ${e.message}")
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|
||||||
val fontDefFile = File(appInfo.dataDir, "${Constants.FILES_DIR}/fonts.json")
|
val fontDefFile = File(appInfo.dataDir, "${Constants.FILES_DIR}/fonts.json").apply { asFile() }
|
||||||
if (!fontDefFile.exists()) return@with
|
if (!fontDefFile.exists()) return@with
|
||||||
|
|
||||||
val fontDef = try {
|
val fontDef = try {
|
||||||
JSON.decodeFromString<FontDefinition>(fontDefFile.readText())
|
JSON.decodeFromString<FontDefinition>(fontDefFile.readText())
|
||||||
} catch (e: Throwable) {
|
} catch (_: Throwable) {
|
||||||
Log.w("fonts.json malformed: ${e.message}")
|
|
||||||
return@with
|
return@with
|
||||||
}
|
}
|
||||||
val setName = fontDef.name ?: return@with
|
|
||||||
|
|
||||||
val downloadsDir = File(appInfo.dataDir, "${Constants.FILES_DIR}/downloads/fonts").asDir()
|
fontsDownloadsDir = File(appInfo.dataDir, "${Constants.FILES_DIR}/downloads/fonts").apply { asDir() }
|
||||||
fontsDir = File(downloadsDir, setName).asDir()
|
fontsDir = File(fontsDownloadsDir, fontDef.name!!).apply { asDir() }
|
||||||
fontsDownloadsDir = downloadsDir
|
|
||||||
fontsAbsPath = fontsDir.absolutePath + "/"
|
fontsAbsPath = fontsDir.absolutePath + "/"
|
||||||
|
|
||||||
// Prune stale font files for this set.
|
|
||||||
fontsDir.listFiles()?.forEach { file ->
|
fontsDir.listFiles()?.forEach { file ->
|
||||||
val fileName = file.name
|
val fileName = file.name
|
||||||
if (!fileName.startsWith(".")) {
|
if (!fileName.startsWith(".")) {
|
||||||
val fontName = fileName.split('.')[0]
|
val fontName = fileName.split('.')[0]
|
||||||
if (fontDef.main.keys.none { it == fontName }) {
|
if (fontDef.main.keys.none { it == fontName }) {
|
||||||
Log.i("Deleting stale font file: $fileName")
|
Log.i("Deleting font file: $fileName")
|
||||||
file.delete()
|
file.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Font files are normally downloaded by the JS side; this is a safety net for
|
// These files should be downloaded by the JS side, but oh well
|
||||||
// font packs that only ship a fonts.json pointing at remote URLs.
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
fontDef.main.entries.map { (name, url) ->
|
fontDef.main.keys.map { name ->
|
||||||
async {
|
async {
|
||||||
|
val url = fontDef.main.getValue(name)
|
||||||
try {
|
try {
|
||||||
Log.i("Downloading $name from $url")
|
Log.i("Downloading $name from $url")
|
||||||
val ext = FILE_EXTENSIONS.firstOrNull { url.endsWith(it) } ?: ".ttf"
|
val file =
|
||||||
val file = File(fontsDir, "$name$ext")
|
File(fontsDir, "$name${FILE_EXTENSIONS.first { url.endsWith(it) }}").apply { asFile() }
|
||||||
if (file.exists()) return@async
|
if (file.exists()) return@async
|
||||||
|
|
||||||
val response: HttpResponse = HttpClient(CIO) {
|
val client = HttpClient(CIO) {
|
||||||
install(UserAgent) { agent = Constants.USER_AGENT }
|
install(UserAgent) { agent = Constants.USER_AGENT }
|
||||||
install(HttpRedirect) {}
|
|
||||||
}.use { it.get(url) }
|
|
||||||
|
|
||||||
if (response.status == HttpStatusCode.OK) {
|
|
||||||
file.writeBytes(response.body())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val response: HttpResponse = client.get(url)
|
||||||
|
if (response.status == HttpStatusCode.OK) file.writeBytes(response.body())
|
||||||
|
|
||||||
|
return@async
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Log.e("Failed to download font ($name from $url)", e)
|
Log.e("Failed to download fonts ($name from $url)", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.awaitAll()
|
}.awaitAll()
|
||||||
|
|
@ -138,68 +119,69 @@ object FontsModule : Module() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createAssetTypefaceWithFallbacks(
|
private fun createAssetTypefaceWithFallbacks(
|
||||||
fontFamilyNames: Array<String>,
|
fontFamilyNames: Array<String>, style: Int, assetManager: AssetManager
|
||||||
style: Int,
|
|
||||||
assetManager: AssetManager,
|
|
||||||
): Typeface? {
|
): Typeface? {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return null
|
|
||||||
|
|
||||||
val fontFamilies: MutableList<FontFamily> = ArrayList()
|
val fontFamilies: MutableList<FontFamily> = ArrayList()
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
// Iterate over the list of fontFamilyNames, constructing new FontFamily objects
|
// Iterate over the list of fontFamilyNames, constructing new FontFamily objects
|
||||||
// for use in the CustomFallbackBuilder below.
|
// for use in the CustomFallbackBuilder below.
|
||||||
for (fontFamilyName in fontFamilyNames) {
|
for (fontFamilyName in fontFamilyNames) {
|
||||||
try {
|
try {
|
||||||
for (fileExt in FILE_EXTENSIONS) {
|
for (fileExtension in FILE_EXTENSIONS) {
|
||||||
val split = fontFamilyName.split(":")
|
val (customName, refName) = fontFamilyName.split(":")
|
||||||
if (split.size != 2) break
|
val file = File(fontsDownloadsDir, "$customName/$refName.$fileExtension").apply { asFile() }
|
||||||
val (customName, refName) = split
|
val font = Font.Builder(file).build()
|
||||||
val downloads = fontsDownloadsDir ?: break
|
val family = FontFamily.Builder(font).build()
|
||||||
val file = File(downloads, "$customName/$refName.$fileExt")
|
fontFamilies.add(family)
|
||||||
if (!file.exists()) continue
|
}
|
||||||
val font = Font.Builder(file).build()
|
} catch (_: Throwable) {
|
||||||
fontFamilies.add(FontFamily.Builder(font).build())
|
|
||||||
}
|
}
|
||||||
} catch (_: Throwable) {
|
|
||||||
}
|
|
||||||
|
|
||||||
for (fontRootPath in arrayOf(fontsAbsPath, FONTS_ASSET_PATH).filterNotNull()) {
|
for (fontRootPath in arrayOf(fontsAbsPath, FONTS_ASSET_PATH).filterNotNull()) {
|
||||||
for (fileExt in FILE_EXTENSIONS) {
|
for (fileExtension in FILE_EXTENSIONS) {
|
||||||
val fileName = "$fontRootPath$fontFamilyName$fileExt"
|
val fileName =
|
||||||
try {
|
StringBuilder().append(fontRootPath).append(fontFamilyName).append(fileExtension).toString()
|
||||||
val builder = if (fileName[0] == '/') Font.Builder(File(fileName))
|
try {
|
||||||
else Font.Builder(assetManager, fileName)
|
val builder = if (fileName[0] == '/') Font.Builder(File(fileName)) else Font.Builder(
|
||||||
val font = builder.build()
|
assetManager, fileName
|
||||||
fontFamilies.add(FontFamily.Builder(font).build())
|
)
|
||||||
} catch (_: RuntimeException) {
|
val font = builder.build()
|
||||||
continue
|
val family = FontFamily.Builder(font).build()
|
||||||
} catch (_: IOException) {
|
fontFamilies.add(family)
|
||||||
continue
|
} catch (_: RuntimeException) {
|
||||||
|
// If the typeface asset does not exist, try another extension.
|
||||||
|
continue
|
||||||
|
} catch (_: 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 there's some problem constructing fonts, fall back to the default behavior.
|
||||||
if (fontFamilies.isEmpty()) return createAssetTypeface(fontFamilyNames[0], style, assetManager)
|
if (fontFamilies.isEmpty()) return createAssetTypeface(fontFamilyNames[0], style, assetManager)
|
||||||
|
|
||||||
val fallbackBuilder = CustomFallbackBuilder(fontFamilies[0])
|
val fallbackBuilder = CustomFallbackBuilder(fontFamilies[0])
|
||||||
for (i in 1 until fontFamilies.size) {
|
for (i in 1 until fontFamilies.size) {
|
||||||
fallbackBuilder.addCustomFallback(fontFamilies[i])
|
fallbackBuilder.addCustomFallback(fontFamilies[i])
|
||||||
|
}
|
||||||
|
return fallbackBuilder.build()
|
||||||
}
|
}
|
||||||
return fallbackBuilder.build()
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createAssetTypeface(
|
private fun createAssetTypeface(
|
||||||
rawName: String,
|
fontFamilyName: String, style: Int, assetManager: AssetManager
|
||||||
style: Int,
|
|
||||||
assetManager: AssetManager,
|
|
||||||
): Typeface? {
|
): Typeface? {
|
||||||
// This logic attempts to safely check if the frontend code is attempting to use
|
// 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.
|
// fallback fonts, and if it is, to use the fallback typeface creation logic.
|
||||||
val fontFamilyNames = rawName.split(",").map { it.trim() }.filter { it.isNotEmpty() }.toTypedArray()
|
var fontFamilyName: String = fontFamilyName
|
||||||
|
val fontFamilyNames = fontFamilyName.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
|
for (i in fontFamilyNames.indices) {
|
||||||
|
fontFamilyNames[i] = fontFamilyNames[i].trim()
|
||||||
|
}
|
||||||
|
|
||||||
var fontFamilyName = rawName
|
|
||||||
// If there are multiple font family names:
|
// If there are multiple font family names:
|
||||||
// For newer versions of Android, construct a Typeface with fallbacks
|
// 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
|
// For older versions of Android, ignore all the fallbacks and just use the first font family
|
||||||
|
|
@ -211,16 +193,13 @@ object FontsModule : Module() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val extension = EXTENSIONS.getOrElse(style) { "" }
|
val extension = EXTENSIONS[style]
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (fileExt in FILE_EXTENSIONS) {
|
for (fileExtension in FILE_EXTENSIONS) {
|
||||||
val split = fontFamilyName.split(":")
|
val (customName, refName) = fontFamilyName.split(":")
|
||||||
if (split.size != 2) break
|
val file = File(fontsDownloadsDir, "$customName/$refName.$fileExtension").apply { asFile() }
|
||||||
val (customName, refName) = split
|
if (!file.exists()) throw Exception()
|
||||||
val downloads = fontsDownloadsDir ?: break
|
|
||||||
val file = File(downloads, "$customName/$refName.$fileExt")
|
|
||||||
if (!file.exists()) continue
|
|
||||||
return Typeface.createFromFile(file.absolutePath)
|
return Typeface.createFromFile(file.absolutePath)
|
||||||
}
|
}
|
||||||
} catch (_: Throwable) {
|
} catch (_: Throwable) {
|
||||||
|
|
@ -229,8 +208,11 @@ object FontsModule : Module() {
|
||||||
// Lastly, after all those checks above, this is the original RN logic for
|
// Lastly, after all those checks above, this is the original RN logic for
|
||||||
// getting the typeface.
|
// getting the typeface.
|
||||||
for (fontRootPath in arrayOf(fontsAbsPath, FONTS_ASSET_PATH).filterNotNull()) {
|
for (fontRootPath in arrayOf(fontsAbsPath, FONTS_ASSET_PATH).filterNotNull()) {
|
||||||
for (fileExt in FILE_EXTENSIONS) {
|
for (fileExtension in FILE_EXTENSIONS) {
|
||||||
val fileName = "$fontRootPath$fontFamilyName$extension$fileExt"
|
val fileName =
|
||||||
|
StringBuilder().append(fontRootPath).append(fontFamilyName).append(extension).append(fileExtension)
|
||||||
|
.toString()
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
if (fileName[0] == '/') Typeface.createFromFile(fileName)
|
if (fileName[0] == '/') Typeface.createFromFile(fileName)
|
||||||
else Typeface.createFromAsset(assetManager, fileName)
|
else Typeface.createFromAsset(assetManager, fileName)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue