Skip to content

Commit

Permalink
refactor(legacy): Fonts API cleanup
Browse files Browse the repository at this point in the history
no reflections!
  • Loading branch information
MukjepScarlet committed Dec 20, 2024
1 parent f406af2 commit 3a8edad
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 155 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ object SettingsUtils {
* @param script The script to apply.
*/
fun applyScript(script: String) {
script.lines().forEachIndexed { index, s ->
script.lineSequence().forEachIndexed { index, s ->
if (s.isEmpty() || s.startsWith('#')) {
return@forEachIndexed
}
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/net/ccbluex/liquidbounce/config/Value.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import net.minecraft.client.gui.FontRenderer
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

abstract class Value<T>(
sealed class Value<T>(
val name: String,
protected open var value: T,
val subjective: Boolean = false,
Expand Down Expand Up @@ -309,7 +309,7 @@ open class FontValue(
val valueObject = JsonObject()
valueObject.run {
addProperty("fontName", fontDetails.name)
addProperty("fontSize", fontDetails.fontSize)
addProperty("fontSize", fontDetails.size)
}
return valueObject
}
Expand All @@ -327,7 +327,7 @@ open class FontValue(
else -> {
val fontInfo = Fonts.getFontDetails(value)
fontInfo?.let {
"${it.name}${if (it.fontSize != -1) " - ${it.fontSize}" else ""}"
"${it.name}${if (it.size != -1) " - ${it.size}" else ""}"
} ?: "Font: Unknown"
}
}
Expand Down
10 changes: 0 additions & 10 deletions src/main/java/net/ccbluex/liquidbounce/ui/font/FontDetails.kt

This file was deleted.

208 changes: 67 additions & 141 deletions src/main/java/net/ccbluex/liquidbounce/ui/font/Fonts.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,190 +6,116 @@
package net.ccbluex.liquidbounce.ui.font

import com.google.gson.JsonArray
import com.google.gson.JsonNull
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import net.ccbluex.liquidbounce.LiquidBounce.CLIENT_CLOUD
import net.ccbluex.liquidbounce.file.FileManager.PRETTY_GSON
import net.ccbluex.liquidbounce.file.FileManager.fontsDir
import net.ccbluex.liquidbounce.utils.client.ClientUtils.LOGGER
import net.ccbluex.liquidbounce.utils.client.MinecraftInstance
import net.ccbluex.liquidbounce.utils.io.HttpUtils.download
import net.ccbluex.liquidbounce.utils.io.extractZipTo
import net.ccbluex.liquidbounce.utils.io.jsonArrayOf
import net.ccbluex.liquidbounce.utils.io.readJson
import net.ccbluex.liquidbounce.utils.io.writeJson
import net.minecraft.client.gui.FontRenderer
import java.awt.Font
import java.io.File
import java.io.IOException
import java.nio.file.Paths
import java.util.zip.ZipInputStream
import kotlin.io.path.inputStream
import kotlin.system.measureTimeMillis

data class FontInfo(val name: String, val size: Int = -1, val isCustom: Boolean = false) {
constructor(font: Font) : this(font.name, font.size)
}

private val FONT_REGISTRY = LinkedHashMap<FontInfo, FontRenderer>()

object Fonts : MinecraftInstance() {

@FontDetails(fontName = "Minecraft Font")
val minecraftFont: FontRenderer = mc.fontRendererObj

@FontDetails(fontName = "Roboto Medium", fontSize = 35)
lateinit var font35: GameFontRenderer

@FontDetails(fontName = "Roboto Medium", fontSize = 40)
lateinit var font40: GameFontRenderer

@FontDetails(fontName = "Roboto Bold", fontSize = 180)
lateinit var fontBold180: GameFontRenderer

private val CUSTOM_FONT_RENDERERS = hashMapOf<FontInfo, FontRenderer>()
private fun <T : FontRenderer> register(fontInfo: FontInfo, fontRenderer: T): T {
FONT_REGISTRY[fontInfo] = fontRenderer
return fontRenderer
}

fun loadFonts() {
val l = System.currentTimeMillis()
LOGGER.info("Loading Fonts.")
val time = measureTimeMillis {
downloadFonts()

register(FontInfo(name = "Minecraft Font"), minecraftFont)

font35 = register(FontInfo(name = "Roboto Medium", size = 35),
GameFontRenderer(getFontFromFile("Roboto-Medium.ttf", 35)))

font40 = register(FontInfo(name = "Roboto Medium", size = 40),
GameFontRenderer(getFontFromFile("Roboto-Medium.ttf", 40)))

fontBold180 = register(FontInfo(name = "Roboto Bold", size = 180),
GameFontRenderer(getFontFromFile("Roboto-Bold.ttf", 180)))

loadCustomFonts()
}
LOGGER.info("Loaded Fonts. (${time}ms)")
}

downloadFonts()
font35 = GameFontRenderer(getFont("Roboto-Medium.ttf", 35))
font40 = GameFontRenderer(getFont("Roboto-Medium.ttf", 40))
fontBold180 = GameFontRenderer(getFont("Roboto-Bold.ttf", 180))

try {
CUSTOM_FONT_RENDERERS.clear()
val fontsFile = File(fontsDir, "fonts.json")
if (fontsFile.exists()) {
val jsonElement = JsonParser().parse(fontsFile.bufferedReader())
if (jsonElement is JsonNull) return
val jsonArray = jsonElement as JsonArray
for (element in jsonArray) {
if (element is JsonNull) return
val fontObject = element as JsonObject
val font = getFont(fontObject["fontFile"].asString, fontObject["fontSize"].asInt)
CUSTOM_FONT_RENDERERS[FontInfo(font)] = GameFontRenderer(font)
private fun loadCustomFonts() {
FONT_REGISTRY.keys.removeIf { it.isCustom }

File(fontsDir, "fonts.json").apply {
if (exists()) {
val jsonElement = readJson()

if (jsonElement !is JsonArray) return@apply

for (element in jsonElement) {
if (element !is JsonObject) return@apply

val font = getFontFromFile(element["fontFile"].asString, element["fontSize"].asInt)

FONT_REGISTRY[FontInfo(font)] = GameFontRenderer(font)
}
} else {
fontsFile.createNewFile()

fontsFile.writeText(PRETTY_GSON.toJson(JsonArray()))
createNewFile()
writeJson(jsonArrayOf())
}
} catch (e: Exception) {
e.printStackTrace()
}

LOGGER.info("Loaded Fonts. (" + (System.currentTimeMillis() - l) + "ms)")
}

private fun downloadFonts() {
try {
val outputFile = File(fontsDir, "roboto.zip")
if (!outputFile.exists()) {
LOGGER.info("Downloading fonts...")
download("$CLIENT_CLOUD/fonts/Roboto.zip", outputFile)
LOGGER.info("Extract fonts...")
extractZip(outputFile.path, fontsDir.path)
}
} catch (e: IOException) {
e.printStackTrace()
val outputFile = File(fontsDir, "roboto.zip")
if (!outputFile.exists()) {
LOGGER.info("Downloading fonts...")
download("$CLIENT_CLOUD/fonts/Roboto.zip", outputFile)
LOGGER.info("Extracting fonts...")
outputFile.extractZipTo(fontsDir)
}
}

fun getFontRenderer(name: String, size: Int): FontRenderer {
for (field in Fonts::class.java.declaredFields) {
try {
field.isAccessible = true
val obj = field[null]
if (obj is FontRenderer) {
val fontDetails = field.getAnnotation(FontDetails::class.java)
if (fontDetails.fontName == name && fontDetails.fontSize == size) return obj
}
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
return CUSTOM_FONT_RENDERERS.getOrDefault(FontInfo(name, size), minecraftFont)
return FONT_REGISTRY.entries.firstOrNull { (fontInfo, _) ->
fontInfo.size == size && fontInfo.name.equals(name, true)
}?.value ?: minecraftFont
}

fun getFontDetails(fontRenderer: FontRenderer): FontInfo? {
for (field in Fonts::class.java.declaredFields) {
try {
field.isAccessible = true
val obj = field[null]
if (obj == fontRenderer) {
val fontDetails = field.getAnnotation(FontDetails::class.java)
return FontInfo(fontDetails.fontName, fontDetails.fontSize)
}
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
for ((key, value) in CUSTOM_FONT_RENDERERS) {
if (value === fontRenderer) return key
}
return null
return FONT_REGISTRY.keys.firstOrNull { FONT_REGISTRY[it] == fontRenderer }
}

val fonts: List<FontRenderer>
get() {
val fonts = mutableListOf<FontRenderer>()
for (fontField in Fonts::class.java.declaredFields) {
try {
fontField.isAccessible = true
val fontObj = fontField[null]
if (fontObj is FontRenderer) fonts += fontObj
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
fonts += CUSTOM_FONT_RENDERERS.values
return fonts
}
get() = FONT_REGISTRY.values.toList()

private fun getFont(fontName: String, size: Int) =
try {
val inputStream = File(fontsDir, fontName).inputStream()
var awtClientFont = Font.createFont(Font.TRUETYPE_FONT, inputStream)
awtClientFont = awtClientFont.deriveFont(Font.PLAIN, size.toFloat())
inputStream.close()
awtClientFont
} catch (e: Exception) {
e.printStackTrace()
Font("default", Font.PLAIN, size)
}

private fun extractZip(zipFile: String, outputFolder: String) {
val buffer = ByteArray(1024)
try {
val folder = File(outputFolder)
if (!folder.exists()) folder.mkdir()
val zipInputStream = ZipInputStream(Paths.get(zipFile).inputStream())
var zipEntry = zipInputStream.nextEntry
while (zipEntry != null) {
val newFile = File(outputFolder + File.separator + zipEntry.name)
File(newFile.parent).mkdirs()
val fileOutputStream = newFile.outputStream()
var i: Int
while (zipInputStream.read(buffer).also { i = it } > 0) fileOutputStream.write(buffer, 0, i)
fileOutputStream.close()
zipEntry = zipInputStream.nextEntry
}
zipInputStream.closeEntry()
zipInputStream.close()
} catch (e: IOException) {
e.printStackTrace()
private fun getFontFromFile(fontName: String, size: Int): Font = try {
File(fontsDir, fontName).inputStream().use { inputStream ->
Font.createFont(Font.TRUETYPE_FONT, inputStream).deriveFont(Font.PLAIN, size.toFloat())
}
} catch (e: Exception) {
LOGGER.warn("Exception during loading font[name=${fontName}, size=${size}]", e)
Font("default", Font.PLAIN, size)
}

class FontInfo(val name: String?, val fontSize: Int) {

constructor(font: Font) : this(font.name, font.size)

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || javaClass != other.javaClass) return false

val fontInfo = other as FontInfo

return fontSize == fontInfo.fontSize && name == fontInfo.name
}

override fun hashCode(): Int {
var result = name?.hashCode() ?: 0
result = 31 * result + fontSize
return result
}
}
}
22 changes: 22 additions & 0 deletions src/main/java/net/ccbluex/liquidbounce/utils/io/GsonExtensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package net.ccbluex.liquidbounce.utils.io

import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonParser
import net.ccbluex.liquidbounce.file.FileManager.PRETTY_GSON
import java.io.File

private val parser = JsonParser()

private val EMPTY_JSON_ARRAY = JsonArray()

fun jsonArrayOf(): JsonArray = EMPTY_JSON_ARRAY

fun jsonArrayOf(vararg elements: JsonElement): JsonArray = JsonArray(elements.size).apply { elements.forEach { add(it) } }

fun File.writeJson(content: JsonElement, gson: Gson = PRETTY_GSON) = gson.toJson(content, bufferedWriter())

fun File.readJson(): JsonElement = parser.parse(bufferedReader())


32 changes: 32 additions & 0 deletions src/main/java/net/ccbluex/liquidbounce/utils/io/ZipExtensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package net.ccbluex.liquidbounce.utils.io

import java.io.File
import java.util.zip.ZipInputStream

private fun ZipInputStream.entrySequence() = generateSequence { nextEntry }

fun File.extractZipTo(outputFolder: File) {
require(this.isFile) { "You can only extract from a file." }
require(outputFolder.isDirectory) { "You can only extract zip to a directory." }

outputFolder.apply {
if (!exists()) mkdirs()
}

ZipInputStream(inputStream()).use { zis ->
zis.entrySequence().forEach { entry ->
val newFile = File(outputFolder, entry.name)

if (!newFile.canonicalPath.startsWith(outputFolder.canonicalPath)) {
throw SecurityException("Illegal Zip Entry:${entry.name}")
}

if (entry.isDirectory) {
newFile.mkdirs()
} else {
newFile.parentFile.mkdirs()
zis.copyTo(newFile.outputStream())
}
}
}
}

0 comments on commit 3a8edad

Please sign in to comment.