Modding: allow mods to supply custom fonts (#8715)

* Modding: allow mods to supply custom fonts

* Cleanup

* Code cleanup

---------

Co-authored-by: vegeta1k95 <vfylfhby>
This commit is contained in:
vegeta1k95 2023-02-21 22:09:11 +01:00 committed by GitHub
parent 96fdbbff09
commit c593056e42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 217 additions and 101 deletions

View File

@ -19,7 +19,6 @@ import com.unciv.logic.files.UncivFiles
import com.unciv.logic.event.EventBus import com.unciv.logic.event.EventBus
import com.unciv.ui.screens.basescreen.UncivStage import com.unciv.ui.screens.basescreen.UncivStage
import com.unciv.ui.screens.basescreen.BaseScreen import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.components.Fonts
import com.unciv.utils.Log import com.unciv.utils.Log
import com.unciv.utils.concurrency.Concurrency import com.unciv.utils.concurrency.Concurrency
import java.io.File import java.io.File
@ -41,7 +40,6 @@ open class AndroidLauncher : AndroidApplication() {
} }
val settings = UncivFiles.getSettingsForPlatformLaunchers(filesDir.path) val settings = UncivFiles.getSettingsForPlatformLaunchers(filesDir.path)
val fontFamily = settings.fontFamily
// Manage orientation lock and display cutout // Manage orientation lock and display cutout
val platformSpecificHelper = PlatformSpecificHelpersAndroid(this) val platformSpecificHelper = PlatformSpecificHelpersAndroid(this)
@ -51,7 +49,7 @@ open class AndroidLauncher : AndroidApplication() {
val androidParameters = UncivGameParameters( val androidParameters = UncivGameParameters(
crashReportSysInfo = CrashReportSysInfoAndroid, crashReportSysInfo = CrashReportSysInfoAndroid,
fontImplementation = NativeFontAndroid((Fonts.ORIGINAL_FONT_SIZE * settings.fontSizeMultiplier).toInt(), fontFamily), fontImplementation = FontAndroid(),
customFileLocationHelper = customFileLocationHelper, customFileLocationHelper = customFileLocationHelper,
platformSpecificHelper = platformSpecificHelper platformSpecificHelper = platformSpecificHelper
) )

View File

@ -9,55 +9,95 @@ import android.graphics.fonts.FontFamily
import android.graphics.fonts.FontStyle import android.graphics.fonts.FontStyle
import android.graphics.fonts.SystemFonts import android.graphics.fonts.SystemFonts
import android.os.Build import android.os.Build
import androidx.annotation.RequiresApi
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Pixmap import com.badlogic.gdx.graphics.Pixmap
import com.unciv.ui.components.FontFamilyData import com.unciv.ui.components.FontFamilyData
import com.unciv.ui.components.NativeFontImplementation import com.unciv.ui.components.FontImplementation
import com.unciv.ui.components.Fonts
import java.util.* import java.util.*
import kotlin.math.abs import kotlin.math.abs
/** class FontAndroid : FontImplementation {
* Created by tian on 2016/10/2.
*/ private val fontList: HashSet<Font> = hashSetOf()
class NativeFontAndroid( private val paint: Paint = Paint()
private val size: Int, private var currentFontFamily: String? = null
private val fontFamily: String
) : NativeFontImplementation { init {
private val fontList by lazy{ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) emptySet() fontList.addAll(SystemFonts.getAvailableFonts())
else SystemFonts.getAvailableFonts() paint.isAntiAlias = true
paint.strokeWidth = 0f
paint.setARGB(255, 255, 255, 255)
} }
private val paint by lazy{ createPaint() } override fun setFontFamily(fontFamilyData: FontFamilyData, size: Int) {
fun createPaint() = Paint().apply { paint.textSize = size.toFloat()
typeface = if (fontFamily.isNotBlank() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Helper within the VERSION_CODES.Q gate: Evaluate a Font's desirability (lower = better) for a given family. // Don't have to reload typeface if font-family didn't change
fun Font.matchesFamily(family: String): Int { if (currentFontFamily != fontFamilyData.invariantName) {
val name = file?.nameWithoutExtension ?: return Int.MAX_VALUE currentFontFamily = fontFamilyData.invariantName
if (name == family) return 0
if (!name.startsWith("$family-")) return Int.MAX_VALUE // Mod font
if (style.weight == FontStyle.FONT_WEIGHT_NORMAL && style.slant == FontStyle.FONT_SLANT_UPRIGHT) return 1 if (fontFamilyData.filePath != null)
return 2 + {
abs(style.weight - FontStyle.FONT_WEIGHT_NORMAL) / 100 + paint.typeface = createTypefaceCustom(fontFamilyData.filePath!!)
abs(style.slant - FontStyle.FONT_SLANT_UPRIGHT)
} }
// System font
else
{
paint.typeface = createTypefaceSystem(fontFamilyData.invariantName)
}
}
}
private fun createTypefaceSystem(name: String): Typeface {
if (name.isNotBlank() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
{
val font = fontList.mapNotNull { val font = fontList.mapNotNull {
val distanceToRegular = it.matchesFamily(fontFamily) val distanceToRegular = it.matchesFamily(name)
if (distanceToRegular == Int.MAX_VALUE) null else it to distanceToRegular if (distanceToRegular == Int.MAX_VALUE) null else it to distanceToRegular
}.minByOrNull { it.second }?.first }.minByOrNull { it.second }?.first
if (font != null) {
Typeface.CustomFallbackBuilder(FontFamily.Builder(font).build())
.setSystemFallback(fontFamily).build()
} else Typeface.create(fontFamily, Typeface.NORMAL)
} else Typeface.create(fontFamily, Typeface.NORMAL)
isAntiAlias = true if (font != null)
textSize = size.toFloat() {
strokeWidth = 0f return Typeface.CustomFallbackBuilder(FontFamily.Builder(font).build())
setARGB(255, 255, 255, 255) .setSystemFallback(name).build()
}
}
return Typeface.create(name, Typeface.NORMAL)
}
private fun createTypefaceCustom(path: String): Typeface {
return try
{
Typeface.createFromFile(Gdx.files.local(path).file())
}
catch (e: Exception)
{
// Falling back to default
Typeface.create(Fonts.DEFAULT_FONT_FAMILY, Typeface.NORMAL)
}
}
/** Helper within the VERSION_CODES.Q gate: Evaluate a Font's desirability (lower = better) for a given family. */
@RequiresApi(Build.VERSION_CODES.Q)
private fun Font.matchesFamily(family: String): Int {
val name = file?.nameWithoutExtension ?: return Int.MAX_VALUE
if (name == family) return 0
if (!name.startsWith("$family-")) return Int.MAX_VALUE
if (style.weight == FontStyle.FONT_WEIGHT_NORMAL && style.slant == FontStyle.FONT_SLANT_UPRIGHT) return 1
return 2 +
abs(style.weight - FontStyle.FONT_WEIGHT_NORMAL) / 100 +
abs(style.slant - FontStyle.FONT_SLANT_UPRIGHT)
} }
override fun getFontSize(): Int { override fun getFontSize(): Int {
return size return paint.textSize.toInt()
} }
override fun getCharPixmap(char: Char): Pixmap { override fun getCharPixmap(char: Char): Pixmap {
@ -65,7 +105,7 @@ class NativeFontAndroid(
var width = paint.measureText(char.toString()).toInt() var width = paint.measureText(char.toString()).toInt()
var height = (metric.descent - metric.ascent).toInt() var height = (metric.descent - metric.ascent).toInt()
if (width == 0) { if (width == 0) {
height = size height = getFontSize()
width = height width = height
} }
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
@ -84,7 +124,7 @@ class NativeFontAndroid(
return pixmap return pixmap
} }
override fun getAvailableFontFamilies(): Sequence<FontFamilyData> { override fun getSystemFonts(): Sequence<FontFamilyData> {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
return sequenceOf(FontFamilyData("sans-serif"), FontFamilyData("serif"), FontFamilyData("mono")) return sequenceOf(FontFamilyData("sans-serif"), FontFamilyData("serif"), FontFamilyData("mono"))

View File

@ -27,6 +27,7 @@ import com.unciv.ui.audio.MusicController
import com.unciv.ui.audio.MusicMood import com.unciv.ui.audio.MusicMood
import com.unciv.ui.audio.MusicTrackChooserFlags import com.unciv.ui.audio.MusicTrackChooserFlags
import com.unciv.ui.audio.SoundPlayer import com.unciv.ui.audio.SoundPlayer
import com.unciv.ui.components.FontImplementation
import com.unciv.ui.crashhandling.CrashScreen import com.unciv.ui.crashhandling.CrashScreen
import com.unciv.ui.crashhandling.wrapCrashHandlingUnit import com.unciv.ui.crashhandling.wrapCrashHandlingUnit
import com.unciv.ui.images.ImageGetter import com.unciv.ui.images.ImageGetter
@ -73,6 +74,10 @@ object GUI {
return UncivGame.Current.settings return UncivGame.Current.settings
} }
fun getFontImpl(): FontImplementation {
return UncivGame.Current.fontImplementation!!
}
fun isWorldLoaded(): Boolean { fun isWorldLoaded(): Boolean {
return UncivGame.Current.worldScreen != null return UncivGame.Current.worldScreen != null
} }
@ -216,7 +221,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
// Loading available fonts can take a long time on Android phones. // Loading available fonts can take a long time on Android phones.
// Therefore we initialize the lazy parameters in the font implementation, while we're in another thread, to avoid ANRs on main thread // Therefore we initialize the lazy parameters in the font implementation, while we're in another thread, to avoid ANRs on main thread
fontImplementation?.getCharPixmap('S') fontImplementation?.setFontFamily(settings.fontFamilyData, settings.getFontSize())
// This stuff needs to run on the main thread because it needs the GL context // This stuff needs to run on the main thread because it needs the GL context
launchOnGLThread { launchOnGLThread {

View File

@ -4,11 +4,11 @@ import com.unciv.logic.files.CustomFileLocationHelper
import com.unciv.ui.crashhandling.CrashReportSysInfo import com.unciv.ui.crashhandling.CrashReportSysInfo
import com.unciv.ui.components.AudioExceptionHelper import com.unciv.ui.components.AudioExceptionHelper
import com.unciv.ui.components.GeneralPlatformSpecificHelpers import com.unciv.ui.components.GeneralPlatformSpecificHelpers
import com.unciv.ui.components.NativeFontImplementation import com.unciv.ui.components.FontImplementation
class UncivGameParameters(val crashReportSysInfo: CrashReportSysInfo? = null, class UncivGameParameters(val crashReportSysInfo: CrashReportSysInfo? = null,
val cancelDiscordEvent: (() -> Unit)? = null, val cancelDiscordEvent: (() -> Unit)? = null,
val fontImplementation: NativeFontImplementation? = null, val fontImplementation: FontImplementation? = null,
val consoleMode: Boolean = false, val consoleMode: Boolean = false,
val customFileLocationHelper: CustomFileLocationHelper? = null, val customFileLocationHelper: CustomFileLocationHelper? = null,
val platformSpecificHelper: GeneralPlatformSpecificHelpers? = null, val platformSpecificHelper: GeneralPlatformSpecificHelpers? = null,

View File

@ -6,6 +6,7 @@ import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.multiplayer.FriendList import com.unciv.logic.multiplayer.FriendList
import com.unciv.models.UncivSound import com.unciv.models.UncivSound
import com.unciv.ui.components.FontFamilyData
import com.unciv.ui.components.Fonts import com.unciv.ui.components.Fonts
import java.text.Collator import java.text.Collator
import java.time.Duration import java.time.Duration
@ -96,7 +97,7 @@ class GameSettings {
/** Saves the last successful new game's setup */ /** Saves the last successful new game's setup */
var lastGameSetup: GameSetupInfo? = null var lastGameSetup: GameSetupInfo? = null
var fontFamily: String = Fonts.DEFAULT_FONT_FAMILY var fontFamilyData: FontFamilyData = FontFamilyData.default
var fontSizeMultiplier: Float = 1f var fontSizeMultiplier: Float = 1f
var enableEasterEggs: Boolean = true var enableEasterEggs: Boolean = true
@ -139,6 +140,10 @@ class GameSettings {
} }
} }
fun getFontSize(): Int {
return (Fonts.ORIGINAL_FONT_SIZE * fontSizeMultiplier).toInt()
}
fun getCurrentLocale(): Locale { fun getCurrentLocale(): Locale {
if (locale == null) if (locale == null)
updateLocaleFromLanguage() updateLocaleFromLanguage()

View File

@ -12,15 +12,24 @@ import com.badlogic.gdx.graphics.g2d.TextureRegion
import com.badlogic.gdx.utils.Array import com.badlogic.gdx.utils.Array
import com.badlogic.gdx.utils.Disposable import com.badlogic.gdx.utils.Disposable
import com.unciv.Constants import com.unciv.Constants
import com.unciv.GUI
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.images.ImageGetter import com.unciv.ui.images.ImageGetter
import com.unciv.ui.screens.basescreen.BaseScreen
interface NativeFontImplementation {
interface FontImplementation {
fun setFontFamily(fontFamilyData: FontFamilyData, size: Int)
fun getFontSize(): Int fun getFontSize(): Int
fun getCharPixmap(char: Char): Pixmap fun getCharPixmap(char: Char): Pixmap
fun getAvailableFontFamilies(): Sequence<FontFamilyData> fun getSystemFonts(): Sequence<FontFamilyData>
fun getBitmapFont(): BitmapFont {
val fontData = NativeBitmapFontData(this)
val font = BitmapFont(fontData, fontData.regions, false)
font.setOwnsTexture(true)
return font
}
} }
// If save in `GameSettings` need use invariantFamily. // If save in `GameSettings` need use invariantFamily.
@ -28,8 +37,13 @@ interface NativeFontImplementation {
// If save localName in `GameSettings` may generate garbled characters by encoding. // If save localName in `GameSettings` may generate garbled characters by encoding.
class FontFamilyData( class FontFamilyData(
val localName: String, val localName: String,
val invariantName: String = localName val invariantName: String = localName,
val filePath: String? = null
) { ) {
// For serialization
constructor() : this(default.localName, default.invariantName)
// Implement kotlin equality contract such that _only_ the invariantName field is compared. // Implement kotlin equality contract such that _only_ the invariantName field is compared.
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
return if (other is FontFamilyData) invariantName == other.invariantName return if (other is FontFamilyData) invariantName == other.invariantName
@ -39,16 +53,16 @@ class FontFamilyData(
override fun hashCode() = invariantName.hashCode() override fun hashCode() = invariantName.hashCode()
/** For SelectBox usage */ /** For SelectBox usage */
override fun toString() = localName override fun toString() = localName.tr()
companion object { companion object {
val default = FontFamilyData("Default Font".tr(), Fonts.DEFAULT_FONT_FAMILY) val default = FontFamilyData("Default Font", Fonts.DEFAULT_FONT_FAMILY)
} }
} }
// This class is loosely based on libgdx's FreeTypeBitmapFontData // This class is loosely based on libgdx's FreeTypeBitmapFontData
class NativeBitmapFontData( class NativeBitmapFontData(
private val fontImplementation: NativeFontImplementation private val fontImplementation: FontImplementation
) : BitmapFontData(), Disposable { ) : BitmapFontData(), Disposable {
val regions: Array<TextureRegion> val regions: Array<TextureRegion>
@ -80,6 +94,8 @@ class NativeBitmapFontData(
// Set space glyph. // Set space glyph.
val spaceGlyph = getGlyph(' ') val spaceGlyph = getGlyph(' ')
spaceXadvance = spaceGlyph.xadvance.toFloat() spaceXadvance = spaceGlyph.xadvance.toFloat()
setScale(Constants.defaultFontSize / Fonts.ORIGINAL_FONT_SIZE)
} }
override fun getGlyph(ch: Char): Glyph { override fun getGlyph(ch: Char): Glyph {
@ -167,29 +183,17 @@ object Fonts {
* Do not call from normal code - reset the Skin instead: `BaseScreen.setSkin()` * Do not call from normal code - reset the Skin instead: `BaseScreen.setSkin()`
*/ */
fun resetFont() { fun resetFont() {
val fontData = NativeBitmapFontData(UncivGame.Current.fontImplementation!!) val settings = GUI.getSettings()
font = BitmapFont(fontData, fontData.regions, false) val fontImpl = GUI.getFontImpl()
font.setOwnsTexture(true) fontImpl.setFontFamily(settings.fontFamilyData, settings.getFontSize())
font.data.setScale(Constants.defaultFontSize / ORIGINAL_FONT_SIZE) font = fontImpl.getBitmapFont()
}
/** This resets all cached font data and allows changing the font */
fun resetFont(newFamily: String) {
try {
val fontImplementationClass = UncivGame.Current.fontImplementation!!::class.java
val fontImplementationConstructor = fontImplementationClass.constructors.first()
val newFontImpl = fontImplementationConstructor.newInstance((ORIGINAL_FONT_SIZE * UncivGame.Current.settings.fontSizeMultiplier).toInt(), newFamily)
if (newFontImpl is NativeFontImplementation)
UncivGame.Current.fontImplementation = newFontImpl
} catch (ex: Exception) {}
BaseScreen.setSkin() // calls our resetFont() - needed - the Skin seems to cache glyphs
} }
/** Reduce the font list returned by platform-specific code to font families (plain variant if possible) */ /** Reduce the font list returned by platform-specific code to font families (plain variant if possible) */
fun getAvailableFontFamilyNames(): Sequence<FontFamilyData> { fun getSystemFonts(): Sequence<FontFamilyData> {
val fontImplementation = UncivGame.Current.fontImplementation val fontImplementation = UncivGame.Current.fontImplementation
?: return emptySequence() ?: return emptySequence()
return fontImplementation.getAvailableFontFamilies() return fontImplementation.getSystemFonts()
.sortedWith(compareBy(UncivGame.Current.settings.getCollatorFromLocale()) { it.localName }) .sortedWith(compareBy(UncivGame.Current.settings.getCollatorFromLocale()) { it.localName })
} }

View File

@ -116,25 +116,57 @@ private fun addFontFamilySelect(table: Table, settings: GameSettings, selectBoxM
// Therefore, _selecting_ an item in a `SelectBox` by an instance of `FontFamilyData` where only the `invariantName` is valid won't work properly. // Therefore, _selecting_ an item in a `SelectBox` by an instance of `FontFamilyData` where only the `invariantName` is valid won't work properly.
// //
// This is why it's _not_ `fontSelectBox.selected = FontFamilyData(settings.fontFamily)` // This is why it's _not_ `fontSelectBox.selected = FontFamilyData(settings.fontFamily)`
val fontToSelect = settings.fontFamily val fontToSelect = settings.fontFamilyData
fontSelectBox.selected = fonts.firstOrNull { it.invariantName == fontToSelect } // will default to first entry if `null` is passed fontSelectBox.selected = fonts.firstOrNull { it.invariantName == fontToSelect.invariantName } // will default to first entry if `null` is passed
selectCell.setActor(fontSelectBox).minWidth(selectBoxMinWidth).pad(10f) selectCell.setActor(fontSelectBox).minWidth(selectBoxMinWidth).pad(10f)
fontSelectBox.onChange { fontSelectBox.onChange {
settings.fontFamily = fontSelectBox.selected.invariantName settings.fontFamilyData = fontSelectBox.selected
Fonts.resetFont(settings.fontFamily)
onFontChange() onFontChange()
} }
} }
// This is a heavy operation and causes ANRs
Concurrency.run("Add Font Select") { Concurrency.run("Add Font Select") {
// This is a heavy operation and causes ANRs
val fonts = Array<FontFamilyData>().apply { val fonts = Array<FontFamilyData>()
add(FontFamilyData.default)
for (font in Fonts.getAvailableFontFamilyNames()) // Add default font
add(font) fonts.add(FontFamilyData.default)
// Add mods fonts
val modsDir = Gdx.files.local("mods/")
for (mod in modsDir.list()) {
// Not a dir, continue
if (!mod.isDirectory)
continue
val modFontsDir = mod.child("fonts")
// Mod doesn't have fonts, continue
if (!modFontsDir.exists())
continue
// Find .ttf files and add construct FontFamilyData
for (fontFile in modFontsDir.list()) {
if (fontFile.extension().lowercase() == "ttf") {
fonts.add(
FontFamilyData(
"${fontFile.nameWithoutExtension()} (${mod.name()})",
fontFile.nameWithoutExtension(),
fontFile.path())
)
}
}
} }
// Add system fonts
for (font in Fonts.getSystemFonts())
fonts.add(font)
launchOnGLThread { loadFontSelect(fonts, selectCell) } launchOnGLThread { loadFontSelect(fonts, selectCell) }
} }
} }
@ -154,10 +186,8 @@ private fun addFontSizeMultiplier(
settings.save() settings.save()
} }
fontSizeSlider.onChange { fontSizeSlider.onChange {
if (!fontSizeSlider.isDragging) { if (!fontSizeSlider.isDragging)
Fonts.resetFont(settings.fontFamily)
onFontChange() onFontChange()
}
} }
table.add(fontSizeSlider).pad(5f).row() table.add(fontSizeSlider).pad(5f).row()
} }

View File

@ -15,7 +15,6 @@ import com.unciv.logic.files.SETTINGS_FILE_NAME
import com.unciv.logic.files.UncivFiles import com.unciv.logic.files.UncivFiles
import com.unciv.models.metadata.ScreenSize import com.unciv.models.metadata.ScreenSize
import com.unciv.models.metadata.WindowState import com.unciv.models.metadata.WindowState
import com.unciv.ui.components.Fonts
import com.unciv.utils.Log import com.unciv.utils.Log
import com.unciv.utils.debug import com.unciv.utils.debug
import java.awt.GraphicsEnvironment import java.awt.GraphicsEnvironment
@ -73,7 +72,7 @@ internal object DesktopLauncher {
val platformSpecificHelper = PlatformSpecificHelpersDesktop(config) val platformSpecificHelper = PlatformSpecificHelpersDesktop(config)
val desktopParameters = UncivGameParameters( val desktopParameters = UncivGameParameters(
cancelDiscordEvent = { discordTimer?.cancel() }, cancelDiscordEvent = { discordTimer?.cancel() },
fontImplementation = NativeFontDesktop((Fonts.ORIGINAL_FONT_SIZE * settings.fontSizeMultiplier).toInt(), settings.fontFamily), fontImplementation = FontDesktop(),
customFileLocationHelper = CustomFileLocationHelperDesktop(), customFileLocationHelper = CustomFileLocationHelperDesktop(),
crashReportSysInfo = CrashReportSysInfoDesktop(), crashReportSysInfo = CrashReportSysInfoDesktop(),
platformSpecificHelper = platformSpecificHelper, platformSpecificHelper = platformSpecificHelper,

View File

@ -1,35 +1,66 @@
package com.unciv.app.desktop package com.unciv.app.desktop
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Pixmap import com.badlogic.gdx.graphics.Pixmap
import com.unciv.ui.components.FontFamilyData import com.unciv.ui.components.FontFamilyData
import com.unciv.ui.components.NativeFontImplementation import com.unciv.ui.components.FontImplementation
import com.unciv.ui.components.Fonts
import java.awt.* import java.awt.*
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.util.* import java.util.*
class NativeFontDesktop(private val size: Int, private val fontFamily: String) :
NativeFontImplementation { class FontDesktop : FontImplementation {
private val font by lazy {
Font(fontFamily, Font.PLAIN, size) private lateinit var font: Font
private lateinit var metric: FontMetrics
override fun setFontFamily(fontFamilyData: FontFamilyData, size: Int) {
// Mod font
if (fontFamilyData.filePath != null)
{
this.font = createFontFromFile(fontFamilyData.filePath!!, size)
}
// System font
else
{
this.font = Font(fontFamilyData.invariantName, Font.PLAIN, size)
}
val bufferedImage = BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR)
val graphics = bufferedImage.createGraphics()
this.metric = graphics.getFontMetrics(font)
graphics.dispose()
} }
private val metric by lazy {
val bi = BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR) private fun createFontFromFile(path: String, size: Int): Font {
val g = bi.createGraphics() var font: Font
g.font = font try
val fontMetrics = g.fontMetrics {
g.dispose() // Try to create and register new font
fontMetrics val fontFile = Gdx.files.local(path).file()
val ge = GraphicsEnvironment.getLocalGraphicsEnvironment()
font = Font.createFont(Font.TRUETYPE_FONT, fontFile).deriveFont(size.toFloat())
ge.registerFont(font)
}
catch (e: Exception)
{
// Fallback to default, if failed.
font = Font(Fonts.DEFAULT_FONT_FAMILY, Font.PLAIN, size)
}
return font
} }
override fun getFontSize(): Int { override fun getFontSize(): Int {
return size return font.size
} }
override fun getCharPixmap(char: Char): Pixmap { override fun getCharPixmap(char: Char): Pixmap {
var width = metric.charWidth(char) var width = metric.charWidth(char)
var height = metric.ascent + metric.descent var height = metric.ascent + metric.descent
if (width == 0) { if (width == 0) {
height = size height = font.size
width = height width = height
} }
val bi = BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR) val bi = BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR)
@ -50,7 +81,7 @@ class NativeFontDesktop(private val size: Int, private val fontFamily: String) :
return pixmap return pixmap
} }
override fun getAvailableFontFamilies(): Sequence<FontFamilyData> { override fun getSystemFonts(): Sequence<FontFamilyData> {
val cjkLanguage = " CJK " +System.getProperty("user.language").uppercase() val cjkLanguage = " CJK " +System.getProperty("user.language").uppercase()
return GraphicsEnvironment.getLocalGraphicsEnvironment().allFonts.asSequence() return GraphicsEnvironment.getLocalGraphicsEnvironment().allFonts.asSequence()
.filter { " CJK " !in it.fontName || cjkLanguage in it.fontName } .filter { " CJK " !in it.fontName || cjkLanguage in it.fontName }

View File

@ -19,7 +19,7 @@ import com.unciv.ui.images.ImageWithCustomSize
import com.unciv.ui.screens.basescreen.BaseScreen import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.components.FontFamilyData import com.unciv.ui.components.FontFamilyData
import com.unciv.ui.components.Fonts import com.unciv.ui.components.Fonts
import com.unciv.ui.components.NativeFontImplementation import com.unciv.ui.components.FontImplementation
import com.unciv.ui.components.extensions.center import com.unciv.ui.components.extensions.center
import com.unciv.ui.components.extensions.toLabel import com.unciv.ui.components.extensions.toLabel
import com.unciv.utils.concurrency.Concurrency import com.unciv.utils.concurrency.Concurrency
@ -62,7 +62,7 @@ object FasterUIDevelopment {
class UIDevGame : Game() { class UIDevGame : Game() {
val game = UncivGame(UncivGameParameters( val game = UncivGame(UncivGameParameters(
fontImplementation = NativeFontDesktop() fontImplementation = FontDesktop()
)) ))
override fun create() { override fun create() {
UncivGame.Current = game UncivGame.Current = game
@ -126,7 +126,7 @@ object FasterUIDevelopment {
} }
class NativeFontDesktop : NativeFontImplementation { class FontDesktop : FontImplementation {
private val font by lazy { private val font by lazy {
Font(Fonts.DEFAULT_FONT_FAMILY, Font.PLAIN, Fonts.ORIGINAL_FONT_SIZE.toInt()) Font(Fonts.DEFAULT_FONT_FAMILY, Font.PLAIN, Fonts.ORIGINAL_FONT_SIZE.toInt())
} }
@ -139,6 +139,10 @@ class NativeFontDesktop : NativeFontImplementation {
fontMetrics fontMetrics
} }
override fun setFontFamily(fontFamilyData: FontFamilyData, size: Int) {
// Empty
}
override fun getFontSize(): Int { override fun getFontSize(): Int {
return Fonts.ORIGINAL_FONT_SIZE.toInt() return Fonts.ORIGINAL_FONT_SIZE.toInt()
} }
@ -168,7 +172,7 @@ class NativeFontDesktop : NativeFontImplementation {
return pixmap return pixmap
} }
override fun getAvailableFontFamilies(): Sequence<FontFamilyData> { override fun getSystemFonts(): Sequence<FontFamilyData> {
return sequenceOf(FontFamilyData(Fonts.DEFAULT_FONT_FAMILY)) return sequenceOf(FontFamilyData(Fonts.DEFAULT_FONT_FAMILY))
} }
} }