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
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
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.
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

View File

@ -567,6 +567,9 @@ Enable portrait orientation = 启用竖屏
Generate translation files = 生成翻译文件
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. = 请注意,翻译是一项基于社区的正在进行的工作,并且是【不完整的】!显示的百分比是语言在游戏中的翻译量。如果您想帮助将游戏翻译成您的语言,请单击此处。
Font family = 字体
Default Font = 默认字体
You need to restart the game for this change to take effect. = 您需要重新启动游戏才能使此更改生效。
# Notifications

View File

@ -567,6 +567,9 @@ Enable portrait orientation =
Generate translation files =
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. =
Font family =
Default Font =
You need to restart the game for this change to take effect. =
# Notifications

View File

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

View File

@ -3,21 +3,44 @@ package com.unciv.app
import android.graphics.Bitmap
import android.graphics.Canvas
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.unciv.ui.utils.FontData
import com.unciv.ui.utils.NativeFontImplementation
/**
* 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 {
return size
}
override fun getCharPixmap(char: Char): Pixmap {
val paint = Paint()
paint.isAntiAlias = true
paint.textSize = size.toFloat()
val metric = paint.fontMetrics
var width = paint.measureText(char.toString()).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 canvas = Canvas(bitmap)
paint.strokeWidth = 0f
paint.setARGB(255, 255, 255, 255)
canvas.drawText(char.toString(), 0f, -metric.ascent, paint)
val pixmap = Pixmap(width, height, Pixmap.Format.RGBA8888)
val data = IntArray(width * height)
@ -42,4 +63,14 @@ class NativeFontAndroid(val size: Int) : NativeFontImplementation {
bitmap.recycle()
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.Gdx
import com.badlogic.gdx.files.FileHandle
import com.unciv.JsonParser
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.GameSaver
import com.unciv.ui.utils.Fonts
import java.text.Collator
import java.util.*
import kotlin.collections.HashSet
import kotlin.io.path.Path
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 */
var lastGameSetup: GameSetupInfo? = null
var fontFamily: String = Fonts.DEFAULT_FONT_FAMILY
init {
// 26 = Android Oreo. Versions below may display permanent icon in notification bar.
if (Gdx.app?.type == Application.ApplicationType.Android && Gdx.app.version < 26) {
@ -104,6 +110,24 @@ class GameSettings {
fun getCollatorFromLocale(): Collator {
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) {

View File

@ -18,6 +18,23 @@ import com.unciv.models.stats.Stat
interface NativeFontImplementation {
fun getFontSize(): Int
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
@ -128,8 +145,9 @@ object Fonts {
* 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. */
const val ORIGINAL_FONT_SIZE = 50f
const val DEFAULT_FONT_FAMILY = ""
lateinit var font:BitmapFont
lateinit var font: BitmapFont
fun resetFont() {
val fontData = NativeBitmapFontData(UncivGame.Current.fontImplementation!!)
font = BitmapFont(fontData, fontData.regions, false)
@ -137,6 +155,11 @@ object Fonts {
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.
*
@ -145,15 +168,15 @@ object Fonts {
* @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
fun extractPixmapFromTextureRegion(textureRegion:TextureRegion):Pixmap {
fun extractPixmapFromTextureRegion(textureRegion: TextureRegion): Pixmap {
val textureData = textureRegion.texture.textureData
if (!textureData.isPrepared) {
textureData.prepare()
}
val pixmap = Pixmap(
textureRegion.regionWidth,
textureRegion.regionHeight,
textureData.format
textureRegion.regionWidth,
textureRegion.regionHeight,
textureData.format
)
val textureDataPixmap = textureData.consumePixmap()
pixmap.drawPixmap(

View File

@ -368,6 +368,8 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) {
}
}
addFontFamilySelect(Fonts.getAvailableFontFamilyNames())
addTranslationGeneration()
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() {
if (Gdx.app.type == Application.ApplicationType.Desktop) {
val generateTranslationsButton = "Generate translation files".toTextButton()

View File

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

View File

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