Custom desktop font (#6377)

* Custom desktop font

* Add `getDesktopAllFonts` to setting custom desktop font.

* Custom font.
`desktopFontFamily` change to `fontFamily`.
Add GameSettings.getSettingsForPlatformLaunchers().

* Add `Custom font` setting UI.

* Add `Custom font` on Android.

* `Default Font` use translations.

* format

* remove open fun.

Co-authored-by: Yair Morgenstern <yairm210@hotmail.com>
This commit is contained in:
Tang 2022-03-21 14:12:16 -05:00 committed by GitHub
parent 130fd653a4
commit c1737b6183
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 153 additions and 26 deletions

View File

@ -562,6 +562,8 @@ Enable portrait orientation = Hochkant-Orientierung zulassen
Generate translation files = Erstelle Übersetzungsdateien Generate translation files = Erstelle Übersetzungsdateien
Translation files are generated successfully. = Die Übersetzungsdateien wurden erfolgreich erstellt. Translation files are generated successfully. = Die Übersetzungsdateien wurden erfolgreich erstellt.
Please note that translations are a community-based work in progress and are INCOMPLETE! The percentage shown is how much of the language is translated in-game. If you want to help translating the game into your language, click here. = Bitte beachte, daß die Übersetzungen eine andauernde Leistung einer Gemeinschaft von Freiwilligen sind und damit oft unvollständig. Die angezeigte Prozentzahl bedeutet den Anteil übersetzter Texte im gesamten Spiel. Wenn Du helfen willst, die Übersetzungen zu verbessern - dies ist ein Link zur Anleitung. Please note that translations are a community-based work in progress and are INCOMPLETE! The percentage shown is how much of the language is translated in-game. If you want to help translating the game into your language, click here. = Bitte beachte, daß die Übersetzungen eine andauernde Leistung einer Gemeinschaft von Freiwilligen sind und damit oft unvollständig. Die angezeigte Prozentzahl bedeutet den Anteil übersetzter Texte im gesamten Spiel. Wenn Du helfen willst, die Übersetzungen zu verbessern - dies ist ein Link zur Anleitung.
Font family = Schriftart
You need to restart the game for this change to take effect. = Diese Änderung wird erst beim nächsten Start des Spiels wirksam.
# Notifications # Notifications

View File

@ -567,6 +567,9 @@ Enable portrait orientation = 启用竖屏
Generate translation files = 生成翻译文件 Generate translation files = 生成翻译文件
Translation files are generated successfully. = 翻译文件生成成功。 Translation files are generated successfully. = 翻译文件生成成功。
Please note that translations are a community-based work in progress and are INCOMPLETE! The percentage shown is how much of the language is translated in-game. If you want to help translating the game into your language, click here. = 请注意,翻译是一项基于社区的正在进行的工作,并且是【不完整的】!显示的百分比是语言在游戏中的翻译量。如果您想帮助将游戏翻译成您的语言,请单击此处。 Please note that translations are a community-based work in progress and are INCOMPLETE! The percentage shown is how much of the language is translated in-game. If you want to help translating the game into your language, click here. = 请注意,翻译是一项基于社区的正在进行的工作,并且是【不完整的】!显示的百分比是语言在游戏中的翻译量。如果您想帮助将游戏翻译成您的语言,请单击此处。
Font family = 字体
Default Font = 默认字体
You need to restart the game for this change to take effect. = 您需要重新启动游戏才能使此更改生效。
# Notifications # Notifications

View File

@ -567,6 +567,9 @@ Enable portrait orientation =
Generate translation files = Generate translation files =
Translation files are generated successfully. = Translation files are generated successfully. =
Please note that translations are a community-based work in progress and are INCOMPLETE! The percentage shown is how much of the language is translated in-game. If you want to help translating the game into your language, click here. = Please note that translations are a community-based work in progress and are INCOMPLETE! The percentage shown is how much of the language is translated in-game. If you want to help translating the game into your language, click here. =
Font family =
Default Font =
You need to restart the game for this change to take effect. =
# Notifications # Notifications

View File

@ -11,6 +11,7 @@ import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.UncivGameParameters import com.unciv.UncivGameParameters
import com.unciv.logic.GameSaver import com.unciv.logic.GameSaver
import com.unciv.models.metadata.GameSettings
import com.unciv.ui.utils.Fonts import com.unciv.ui.utils.Fonts
import java.io.File import java.io.File
@ -34,12 +35,15 @@ open class AndroidLauncher : AndroidApplication() {
val config = AndroidApplicationConfiguration().apply { val config = AndroidApplicationConfiguration().apply {
useImmersiveMode = true; useImmersiveMode = true;
} }
val fontFamily = GameSettings.getSettingsForPlatformLaunchers(filesDir.path).fontFamily
val androidParameters = UncivGameParameters( val androidParameters = UncivGameParameters(
version = BuildConfig.VERSION_NAME, version = BuildConfig.VERSION_NAME,
crashReportSysInfo = CrashReportSysInfoAndroid, crashReportSysInfo = CrashReportSysInfoAndroid,
fontImplementation = NativeFontAndroid(Fonts.ORIGINAL_FONT_SIZE.toInt()), fontImplementation = NativeFontAndroid(Fonts.ORIGINAL_FONT_SIZE.toInt(), fontFamily),
customSaveLocationHelper = customSaveLocationHelper, customSaveLocationHelper = customSaveLocationHelper,
limitOrientationsHelper = limitOrientationsHelper limitOrientationsHelper = limitOrientationsHelper
) )
game = UncivGame(androidParameters) game = UncivGame(androidParameters)

View File

@ -3,21 +3,44 @@ package com.unciv.app
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Paint import android.graphics.Paint
import android.graphics.Typeface
import android.graphics.fonts.Font
import android.graphics.fonts.FontFamily
import android.graphics.fonts.SystemFonts
import android.os.Build
import com.badlogic.gdx.graphics.Pixmap import com.badlogic.gdx.graphics.Pixmap
import com.unciv.ui.utils.FontData
import com.unciv.ui.utils.NativeFontImplementation import com.unciv.ui.utils.NativeFontImplementation
/** /**
* Created by tian on 2016/10/2. * Created by tian on 2016/10/2.
*/ */
class NativeFontAndroid(val size: Int) : NativeFontImplementation { class NativeFontAndroid(private val size: Int, private val fontFamily: String) :
NativeFontImplementation {
private val paint = Paint().apply {
typeface = if (fontFamily.isNotBlank() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val font = fontList.firstOrNull { it.file?.nameWithoutExtension == fontFamily }
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
textSize = size.toFloat()
strokeWidth = 0f
setARGB(255, 255, 255, 255)
}
private val fontList: List<Font>
get() = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) emptyList()
else SystemFonts.getAvailableFonts().toList()
override fun getFontSize(): Int { override fun getFontSize(): Int {
return size return size
} }
override fun getCharPixmap(char: Char): Pixmap { override fun getCharPixmap(char: Char): Pixmap {
val paint = Paint()
paint.isAntiAlias = true
paint.textSize = size.toFloat()
val metric = paint.fontMetrics val metric = paint.fontMetrics
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()
@ -27,8 +50,6 @@ class NativeFontAndroid(val size: Int) : NativeFontImplementation {
} }
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap) val canvas = Canvas(bitmap)
paint.strokeWidth = 0f
paint.setARGB(255, 255, 255, 255)
canvas.drawText(char.toString(), 0f, -metric.ascent, paint) canvas.drawText(char.toString(), 0f, -metric.ascent, paint)
val pixmap = Pixmap(width, height, Pixmap.Format.RGBA8888) val pixmap = Pixmap(width, height, Pixmap.Format.RGBA8888)
val data = IntArray(width * height) val data = IntArray(width * height)
@ -42,4 +63,14 @@ class NativeFontAndroid(val size: Int) : NativeFontImplementation {
bitmap.recycle() bitmap.recycle()
return pixmap return pixmap
} }
override fun getAvailableFont(): Collection<FontData> {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
SystemFonts.getAvailableFonts().mapNotNull {
it.file?.nameWithoutExtension
}.map { FontData(it) }.toSet()
} else {
listOf(FontData("sans-serif"), FontData("serif"), FontData("mono"))
}
}
} }

View File

@ -2,12 +2,16 @@ package com.unciv.models.metadata
import com.badlogic.gdx.Application import com.badlogic.gdx.Application
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import com.unciv.JsonParser
import com.unciv.Constants import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.GameSaver import com.unciv.logic.GameSaver
import com.unciv.ui.utils.Fonts
import java.text.Collator import java.text.Collator
import java.util.* import java.util.*
import kotlin.collections.HashSet import kotlin.collections.HashSet
import kotlin.io.path.Path
data class WindowState (val width: Int = 900, val height: Int = 600) data class WindowState (val width: Int = 900, val height: Int = 600)
@ -65,6 +69,8 @@ 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
init { init {
// 26 = Android Oreo. Versions below may display permanent icon in notification bar. // 26 = Android Oreo. Versions below may display permanent icon in notification bar.
if (Gdx.app?.type == Application.ApplicationType.Android && Gdx.app.version < 26) { if (Gdx.app?.type == Application.ApplicationType.Android && Gdx.app.version < 26) {
@ -104,6 +110,24 @@ class GameSettings {
fun getCollatorFromLocale(): Collator { fun getCollatorFromLocale(): Collator {
return Collator.getInstance(getCurrentLocale()) return Collator.getInstance(getCurrentLocale())
} }
companion object {
/** Specialized function to access settings before Gdx is initialized.
*
* @param base Path to the directory where the file should be - if not set, the OS current directory is used (which is "/" on Android)
*/
fun getSettingsForPlatformLaunchers(base: String = ""): GameSettings {
// FileHandle is Gdx, but the class and JsonParser are not dependent on app initialization
// If fact, at this point Gdx.app or Gdx.files are null but this still works.
val file = FileHandle(Path(base, GameSaver.settingsFileName).toString())
return if (file.exists())
JsonParser().getFromJson(
GameSettings::class.java,
file
)
else GameSettings().apply { isFreshlyCreated = true }
}
}
} }
enum class LocaleCode(var language: String, var country: String) { enum class LocaleCode(var language: String, var country: String) {

View File

@ -18,6 +18,23 @@ import com.unciv.models.stats.Stat
interface NativeFontImplementation { interface NativeFontImplementation {
fun getFontSize(): Int fun getFontSize(): Int
fun getCharPixmap(char: Char): Pixmap fun getCharPixmap(char: Char): Pixmap
fun getAvailableFont(): Collection<FontData>
}
// If save in `GameSettings` need use enName.
// If show to user need use localName.
// If save localName in `GameSettings` may generate garbled characters by encoding.
data class FontData(val localName: String, val enName: String = localName) {
override fun equals(other: Any?): Boolean {
return if (other is FontData) enName == other.enName
else super.equals(other)
}
override fun hashCode(): Int {
var result = localName.hashCode()
result = 31 * result + enName.hashCode()
return result
}
} }
// This class is loosely based on libgdx's FreeTypeBitmapFontData // This class is loosely based on libgdx's FreeTypeBitmapFontData
@ -128,8 +145,9 @@ object Fonts {
* This has several advantages: It means we only render each character once (good for both runtime and RAM), * This has several advantages: It means we only render each character once (good for both runtime and RAM),
* AND it means that our 'custom' emojis only need to be once size (50px) and they'll be rescaled for what's needed. */ * AND it means that our 'custom' emojis only need to be once size (50px) and they'll be rescaled for what's needed. */
const val ORIGINAL_FONT_SIZE = 50f const val ORIGINAL_FONT_SIZE = 50f
const val DEFAULT_FONT_FAMILY = ""
lateinit var font:BitmapFont lateinit var font: BitmapFont
fun resetFont() { fun resetFont() {
val fontData = NativeBitmapFontData(UncivGame.Current.fontImplementation!!) val fontData = NativeBitmapFontData(UncivGame.Current.fontImplementation!!)
font = BitmapFont(fontData, fontData.regions, false) font = BitmapFont(fontData, fontData.regions, false)
@ -137,6 +155,11 @@ object Fonts {
font.data.setScale(Constants.defaultFontSize / ORIGINAL_FONT_SIZE) font.data.setScale(Constants.defaultFontSize / ORIGINAL_FONT_SIZE)
} }
fun getAvailableFontFamilyNames(): Collection<FontData> {
if (UncivGame.Current.fontImplementation == null) return emptyList()
return UncivGame.Current.fontImplementation!!.getAvailableFont()
}
/** /**
* Turn a TextureRegion into a Pixmap. * Turn a TextureRegion into a Pixmap.
* *
@ -145,15 +168,15 @@ object Fonts {
* @return New Pixmap with all the size and pixel data from this TextureRegion copied into it. * @return New Pixmap with all the size and pixel data from this TextureRegion copied into it.
*/ */
// From https://stackoverflow.com/questions/29451787/libgdx-textureregion-to-pixmap // From https://stackoverflow.com/questions/29451787/libgdx-textureregion-to-pixmap
fun extractPixmapFromTextureRegion(textureRegion:TextureRegion):Pixmap { fun extractPixmapFromTextureRegion(textureRegion: TextureRegion): Pixmap {
val textureData = textureRegion.texture.textureData val textureData = textureRegion.texture.textureData
if (!textureData.isPrepared) { if (!textureData.isPrepared) {
textureData.prepare() textureData.prepare()
} }
val pixmap = Pixmap( val pixmap = Pixmap(
textureRegion.regionWidth, textureRegion.regionWidth,
textureRegion.regionHeight, textureRegion.regionHeight,
textureData.format textureData.format
) )
val textureDataPixmap = textureData.consumePixmap() val textureDataPixmap = textureData.consumePixmap()
pixmap.drawPixmap( pixmap.drawPixmap(

View File

@ -368,6 +368,8 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) {
} }
} }
addFontFamilySelect(Fonts.getAvailableFontFamilyNames())
addTranslationGeneration() addTranslationGeneration()
addSetUserId() addSetUserId()
@ -879,6 +881,34 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) {
} }
} }
private fun Table.addFontFamilySelect(fonts: Collection<FontData>) {
if (fonts.isEmpty()) return
add("Font family".toLabel()).left().fillX()
val fontSelectBox = SelectBox<String>(skin)
val fontsLocalName = GdxArray<String>().apply { add("Default Font".tr()) }
val fontsEnName = GdxArray<String>().apply { add("") }
for (font in fonts) {
fontsLocalName.add(font.localName)
fontsEnName.add(font.enName)
}
val selectedIndex = fontsEnName.indexOf(settings.fontFamily).let { if (it == -1) 0 else it }
fontSelectBox.items = fontsLocalName
fontSelectBox.selected = fontsLocalName[selectedIndex]
add(fontSelectBox).minWidth(selectBoxMinWidth).pad(10f).row()
fontSelectBox.onChange {
settings.fontFamily = fontsEnName[fontSelectBox.selectedIndex]
ToastPopup(
"You need to restart the game for this change to take effect.", previousScreen
)
}
}
private fun Table.addTranslationGeneration() { private fun Table.addTranslationGeneration() {
if (Gdx.app.type == Application.ApplicationType.Desktop) { if (Gdx.app.type == Application.ApplicationType.Desktop) {
val generateTranslationsButton = "Generate translation files".toTextButton() val generateTranslationsButton = "Generate translation files".toTextButton()

View File

@ -32,12 +32,10 @@ internal object DesktopLauncher {
config.setWindowIcon("ExtraImages/Icon.png") config.setWindowIcon("ExtraImages/Icon.png")
config.setTitle("Unciv") config.setTitle("Unciv")
config.setHdpiMode(HdpiMode.Logical) config.setHdpiMode(HdpiMode.Logical)
config.setWindowSizeLimits(120, 80, -1, -1); config.setWindowSizeLimits(120, 80, -1, -1)
if (FileHandle(GameSaver.settingsFileName).exists()) {
val settings = JsonParser().getFromJson( val settings = GameSettings.getSettingsForPlatformLaunchers()
GameSettings::class.java, if (!settings.isFreshlyCreated) {
FileHandle(GameSaver.settingsFileName)
)
config.setWindowedMode(settings.windowState.width.coerceAtLeast(120), settings.windowState.height.coerceAtLeast(80)) config.setWindowedMode(settings.windowState.width.coerceAtLeast(120), settings.windowState.height.coerceAtLeast(80))
} }
@ -50,7 +48,7 @@ internal object DesktopLauncher {
val desktopParameters = UncivGameParameters( val desktopParameters = UncivGameParameters(
versionFromJar, versionFromJar,
cancelDiscordEvent = { discordTimer?.cancel() }, cancelDiscordEvent = { discordTimer?.cancel() },
fontImplementation = NativeFontDesktop(Fonts.ORIGINAL_FONT_SIZE.toInt()), fontImplementation = NativeFontDesktop(Fonts.ORIGINAL_FONT_SIZE.toInt(), settings.fontFamily),
customSaveLocationHelper = CustomSaveLocationHelperDesktop() customSaveLocationHelper = CustomSaveLocationHelperDesktop()
) )

View File

@ -1,14 +1,16 @@
package com.unciv.app.desktop package com.unciv.app.desktop
import com.badlogic.gdx.graphics.Pixmap import com.badlogic.gdx.graphics.Pixmap
import com.unciv.ui.utils.FontData
import com.unciv.ui.utils.NativeFontImplementation import com.unciv.ui.utils.NativeFontImplementation
import java.awt.* import java.awt.*
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.util.*
class NativeFontDesktop(private val size: Int, private val fontFamily: String) :
class NativeFontDesktop(private val size: Int) : NativeFontImplementation { NativeFontImplementation {
private val font by lazy { private val font by lazy {
Font("", Font.PLAIN, size) Font(fontFamily, Font.PLAIN, size)
} }
private val metric by lazy { private val metric by lazy {
val bi = BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR) val bi = BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR)
@ -47,4 +49,11 @@ class NativeFontDesktop(private val size: Int) : NativeFontImplementation {
g.dispose() g.dispose()
return pixmap return pixmap
} }
override fun getAvailableFont(): Collection<FontData> {
val allFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().allFonts.map {
FontData(it.fontName, it.getFamily(Locale.ENGLISH))
}.toSet()
return allFonts
}
} }