diff --git a/android/assets/jsons/translations/German.properties b/android/assets/jsons/translations/German.properties index a840f8032d..b9226d1e22 100644 --- a/android/assets/jsons/translations/German.properties +++ b/android/assets/jsons/translations/German.properties @@ -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 diff --git a/android/assets/jsons/translations/Simplified_Chinese.properties b/android/assets/jsons/translations/Simplified_Chinese.properties index 39058cae5a..83c3c54f98 100644 --- a/android/assets/jsons/translations/Simplified_Chinese.properties +++ b/android/assets/jsons/translations/Simplified_Chinese.properties @@ -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 diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 16e3b89fe4..fb1dd564f9 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -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 diff --git a/android/src/com/unciv/app/AndroidLauncher.kt b/android/src/com/unciv/app/AndroidLauncher.kt index c699baa724..b41ac74155 100644 --- a/android/src/com/unciv/app/AndroidLauncher.kt +++ b/android/src/com/unciv/app/AndroidLauncher.kt @@ -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) diff --git a/android/src/com/unciv/app/NativeFontAndroid.kt b/android/src/com/unciv/app/NativeFontAndroid.kt index 2b17e5057f..895d8af924 100755 --- a/android/src/com/unciv/app/NativeFontAndroid.kt +++ b/android/src/com/unciv/app/NativeFontAndroid.kt @@ -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 + 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 { + 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")) + } + } } \ No newline at end of file diff --git a/core/src/com/unciv/models/metadata/GameSettings.kt b/core/src/com/unciv/models/metadata/GameSettings.kt index 5376c61fa2..f36d209308 100644 --- a/core/src/com/unciv/models/metadata/GameSettings.kt +++ b/core/src/com/unciv/models/metadata/GameSettings.kt @@ -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) { diff --git a/core/src/com/unciv/ui/utils/Fonts.kt b/core/src/com/unciv/ui/utils/Fonts.kt index 14b7322185..f1d107f8d8 100644 --- a/core/src/com/unciv/ui/utils/Fonts.kt +++ b/core/src/com/unciv/ui/utils/Fonts.kt @@ -18,6 +18,23 @@ import com.unciv.models.stats.Stat interface NativeFontImplementation { fun getFontSize(): Int fun getCharPixmap(char: Char): Pixmap + fun getAvailableFont(): Collection +} + +// 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 { + 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( diff --git a/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt b/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt index 7ff2ca0b4e..ae167b3de0 100644 --- a/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt +++ b/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt @@ -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) { + if (fonts.isEmpty()) return + + add("Font family".toLabel()).left().fillX() + + val fontSelectBox = SelectBox(skin) + val fontsLocalName = GdxArray().apply { add("Default Font".tr()) } + val fontsEnName = GdxArray().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() diff --git a/desktop/src/com/unciv/app/desktop/DesktopLauncher.kt b/desktop/src/com/unciv/app/desktop/DesktopLauncher.kt index 301af932fa..3051342e26 100644 --- a/desktop/src/com/unciv/app/desktop/DesktopLauncher.kt +++ b/desktop/src/com/unciv/app/desktop/DesktopLauncher.kt @@ -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() ) diff --git a/desktop/src/com/unciv/app/desktop/NativeFontDesktop.kt b/desktop/src/com/unciv/app/desktop/NativeFontDesktop.kt index b186770527..6887cd2af5 100755 --- a/desktop/src/com/unciv/app/desktop/NativeFontDesktop.kt +++ b/desktop/src/com/unciv/app/desktop/NativeFontDesktop.kt @@ -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 { + val allFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().allFonts.map { + FontData(it.fontName, it.getFamily(Locale.ENGLISH)) + }.toSet() + return allFonts + } } \ No newline at end of file