From 4b6bab523fefd88ad3c9376ff563b52e188a8669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A4in=C3=B6=20M=C3=A4kel=C3=A4?= Date: Mon, 18 May 2020 23:09:38 +0300 Subject: [PATCH] Rewrite the font system to render the characters incrementally (#2635) --- android/src/com/unciv/app/AndroidLauncher.kt | 3 +- .../src/com/unciv/app/NativeFontAndroid.kt | 45 +++ .../core/java/nativefont/NativeFontAndroid.kt | 70 ----- core/src/com/unciv/UncivGame.kt | 4 +- core/src/com/unciv/ui/LanguagePickerScreen.kt | 1 - .../unciv/ui/utils/CameraStageBaseScreen.kt | 24 +- core/src/com/unciv/ui/utils/Fonts.kt | 136 +++++---- .../mainmenu/WorldScreenOptionsPopup.kt | 4 +- core/src/core/java/nativefont/NativeFont.kt | 277 ------------------ .../java/nativefont/NativeFontListener.kt | 10 - .../core/java/nativefont/NativeFontPaint.kt | 79 ----- .../com/unciv/app/desktop/DesktopLauncher.kt | 3 +- .../unciv/app/desktop/NativeFontDesktop.kt | 47 +++ .../core/java/nativefont/NativeFontDesktop.kt | 104 ------- 14 files changed, 193 insertions(+), 614 deletions(-) create mode 100755 android/src/com/unciv/app/NativeFontAndroid.kt delete mode 100755 android/src/core/java/nativefont/NativeFontAndroid.kt delete mode 100644 core/src/core/java/nativefont/NativeFont.kt delete mode 100755 core/src/core/java/nativefont/NativeFontListener.kt delete mode 100755 core/src/core/java/nativefont/NativeFontPaint.kt create mode 100755 desktop/src/com/unciv/app/desktop/NativeFontDesktop.kt delete mode 100755 desktop/src/core/java/nativefont/NativeFontDesktop.kt diff --git a/android/src/com/unciv/app/AndroidLauncher.kt b/android/src/com/unciv/app/AndroidLauncher.kt index 56a63e8696..6caadd5f1c 100644 --- a/android/src/com/unciv/app/AndroidLauncher.kt +++ b/android/src/com/unciv/app/AndroidLauncher.kt @@ -23,7 +23,8 @@ class AndroidLauncher : AndroidApplication() { val game = UncivGame ( version = BuildConfig.VERSION_NAME, crashReportSender = CrashReportSenderAndroid(this), - exitEvent = this::finish + exitEvent = this::finish, + fontImplementation = NativeFontAndroid(45) ) initialize(game, config) } diff --git a/android/src/com/unciv/app/NativeFontAndroid.kt b/android/src/com/unciv/app/NativeFontAndroid.kt new file mode 100755 index 0000000000..2b17e5057f --- /dev/null +++ b/android/src/com/unciv/app/NativeFontAndroid.kt @@ -0,0 +1,45 @@ +package com.unciv.app + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Paint +import com.badlogic.gdx.graphics.Pixmap +import com.unciv.ui.utils.NativeFontImplementation + +/** + * Created by tian on 2016/10/2. + */ +class NativeFontAndroid(val size: Int) : NativeFontImplementation { + 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() + if (width == 0) { + height = size + width = height + } + 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) + bitmap.getPixels(data, 0, width, 0, 0, width, height) + for (i in 0 until width) { + for (j in 0 until height) { + pixmap.setColor(Integer.rotateLeft(data[i + (j * width)], 8)) + pixmap.drawPixel(i, j) + } + } + bitmap.recycle() + return pixmap + } +} \ No newline at end of file diff --git a/android/src/core/java/nativefont/NativeFontAndroid.kt b/android/src/core/java/nativefont/NativeFontAndroid.kt deleted file mode 100755 index f695eca359..0000000000 --- a/android/src/core/java/nativefont/NativeFontAndroid.kt +++ /dev/null @@ -1,70 +0,0 @@ -package core.java.nativefont - -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Paint -import android.graphics.Typeface -import com.badlogic.gdx.Gdx -import com.badlogic.gdx.backends.android.AndroidApplication -import com.badlogic.gdx.graphics.Color -import com.badlogic.gdx.graphics.Pixmap -import java.io.ByteArrayOutputStream -import java.util.* - -/** - * Created by tian on 2016/10/2. - */ -class NativeFontAndroid : NativeFontListener { - private val fontFaces = HashMap() - private val androidApplication = Gdx.app as AndroidApplication - override fun getFontPixmap(txt: String, vpaint: NativeFontPaint): Pixmap { - val paint = Paint() - if (vpaint.tTFName != "") { - Gdx.app.log("app", Gdx.files.internal(vpaint.tTFName + - if (vpaint.tTFName.endsWith(".ttf")) "" else ".ttf").file().path) - val fontFace = Typeface.createFromAsset(androidApplication.assets, vpaint.tTFName + - if (vpaint.tTFName.endsWith(".ttf")) "" else ".ttf") - fontFaces[vpaint.tTFName] = fontFace - paint.typeface = fontFace - } - paint.isAntiAlias = true - paint.textSize = vpaint.textSize.toFloat() - val fm = paint.fontMetrics - var w = paint.measureText(txt).toInt() - var h = (fm.descent - fm.ascent).toInt() - if (w == 0) { - h = vpaint.textSize - w = h - } - var bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) - var canvas: Canvas? = Canvas(bitmap) - // 如果是描边类型 - if (vpaint.strokeColor != null) { // 绘制外层 - paint.color = getColor(vpaint.strokeColor) - paint.strokeWidth = vpaint.strokeWidth.toFloat() // 描边宽度 - paint.style = Paint.Style.FILL_AND_STROKE // 描边种类 - paint.isFakeBoldText = true // 外层text采用粗体 - canvas!!.drawText(txt, 0f, -fm.ascent, paint) - paint.isFakeBoldText = false - } else { - paint.isUnderlineText = vpaint.underlineText - paint.isStrikeThruText = vpaint.strikeThruText - paint.isFakeBoldText = vpaint.fakeBoldText - } - // 绘制内层 - paint.strokeWidth = 0f - paint.color = getColor(vpaint.color) - canvas!!.drawText(txt, 0f, -fm.ascent, paint) - val buffer = ByteArrayOutputStream() - bitmap.compress(Bitmap.CompressFormat.PNG, 100, buffer) - val encodedData = buffer.toByteArray() - val pixmap = Pixmap(encodedData, 0, encodedData.size) - buffer.close() - bitmap.recycle() - return pixmap - } - - private fun getColor(color: Color?): Int { - return (color!!.a * 255.0f).toInt() shl 24 or ((color.r * 255.0f).toInt() shl 16) or ((color.g * 255.0f).toInt() shl 8) or (color.b * 255.0f).toInt() - } -} \ No newline at end of file diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index 24ef8921c7..5f997e8c2b 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -22,7 +22,8 @@ class UncivGame( val version: String, private val crashReportSender: CrashReportSender? = null, val exitEvent: (()->Unit)? = null, - val cancelDiscordEvent: (()->Unit)? = null + val cancelDiscordEvent: (()->Unit)? = null, + val fontImplementation: NativeFontImplementation? = null ) : Game() { // we need this secondary constructor because Java code for iOS can't handle Kotlin lambda parameters constructor(version: String) : this(version, null) @@ -94,7 +95,6 @@ class UncivGame( // This stuff needs to run on the main thread because it needs the GL context Gdx.app.postRunnable { - CameraStageBaseScreen.resetFonts() ImageGetter.ruleset = RulesetCache.getBaseRuleset() // so that we can enter the map editor without having to load a game first thread(name="Music") { startMusic() } restoreSize() diff --git a/core/src/com/unciv/ui/LanguagePickerScreen.kt b/core/src/com/unciv/ui/LanguagePickerScreen.kt index c04caf559d..364b3c701c 100644 --- a/core/src/com/unciv/ui/LanguagePickerScreen.kt +++ b/core/src/com/unciv/ui/LanguagePickerScreen.kt @@ -80,7 +80,6 @@ class LanguagePickerScreen(): PickerScreen(){ game.settings.save() game.translations.tryReadTranslationForCurrentLanguage() - resetFonts() game.setScreen(MainMenuScreen()) dispose() } diff --git a/core/src/com/unciv/ui/utils/CameraStageBaseScreen.kt b/core/src/com/unciv/ui/utils/CameraStageBaseScreen.kt index 91ae2796f2..93591adf33 100644 --- a/core/src/com/unciv/ui/utils/CameraStageBaseScreen.kt +++ b/core/src/com/unciv/ui/utils/CameraStageBaseScreen.kt @@ -70,19 +70,19 @@ open class CameraStageBaseScreen : Screen { } companion object { - var skin = Skin(Gdx.files.internal("skin/flat-earth-ui.json")) - - fun resetFonts(){ - skin.get(TextButton.TextButtonStyle::class.java).font = Fonts.getFont(45).apply { data.setScale(20/45f) } - skin.get(CheckBox.CheckBoxStyle::class.java).font= Fonts.getFont(45).apply { data.setScale(20/45f) } + val skin by lazy { + val skin = Skin(Gdx.files.internal("skin/flat-earth-ui.json")) + skin.get(TextButton.TextButtonStyle::class.java).font = Fonts.font.apply { data.setScale(20/45f) } + skin.get(CheckBox.CheckBoxStyle::class.java).font= Fonts.font.apply { data.setScale(20/45f) } skin.get(Label.LabelStyle::class.java).apply { - font = Fonts.getFont(45).apply { data.setScale(18/45f) } + font = Fonts.font.apply { data.setScale(18/45f) } fontColor= Color.WHITE } - skin.get(TextField.TextFieldStyle::class.java).font = Fonts.getFont(45).apply { data.setScale(18/45f) } - skin.get(SelectBox.SelectBoxStyle::class.java).font = Fonts.getFont(45).apply { data.setScale(20/45f) } - skin.get(SelectBox.SelectBoxStyle::class.java).listStyle.font = Fonts.getFont(45).apply { data.setScale(20/45f) } + skin.get(TextField.TextFieldStyle::class.java).font = Fonts.font.apply { data.setScale(18/45f) } + skin.get(SelectBox.SelectBoxStyle::class.java).font = Fonts.font.apply { data.setScale(20/45f) } + skin.get(SelectBox.SelectBoxStyle::class.java).listStyle.font = Fonts.font.apply { data.setScale(20/45f) } skin.get(CheckBox.CheckBoxStyle::class.java).fontColor= Color.WHITE + skin } internal var batch: Batch = SpriteBatch() } @@ -239,7 +239,7 @@ fun String.toLabel(fontColor:Color= Color.WHITE, fontSize:Int=18): Label { if(fontColor!= Color.WHITE || fontSize!=18) { // if we want the default we don't need to create another style labelStyle = Label.LabelStyle(labelStyle) // clone this to another labelStyle.fontColor = fontColor - if (fontSize != 18) labelStyle.font = Fonts.getFont(45) + if (fontSize != 18) labelStyle.font = Fonts.font } return Label(this.tr(),labelStyle).apply { setFontScale(fontSize/45f) } } @@ -249,7 +249,7 @@ fun Label.setFontColor(color:Color): Label {style=Label.LabelStyle(style).apply fun Label.setFontSize(size:Int): Label { style = Label.LabelStyle(style) - style.font = Fonts.getFont(45) + style.font = Fonts.font style = style // because we need it to call the SetStyle function. Yuk, I know. return this.apply { setFontScale(size/45f) } // for chaining -} +} \ No newline at end of file diff --git a/core/src/com/unciv/ui/utils/Fonts.kt b/core/src/com/unciv/ui/utils/Fonts.kt index 538ba1ed1b..4363f70a29 100644 --- a/core/src/com/unciv/ui/utils/Fonts.kt +++ b/core/src/com/unciv/ui/utils/Fonts.kt @@ -1,73 +1,103 @@ package com.unciv.ui.utils +import com.badlogic.gdx.graphics.Color +import com.badlogic.gdx.graphics.Pixmap +import com.badlogic.gdx.graphics.Texture import com.badlogic.gdx.graphics.g2d.BitmapFont +import com.badlogic.gdx.graphics.g2d.BitmapFont.BitmapFontData +import com.badlogic.gdx.graphics.g2d.BitmapFont.Glyph +import com.badlogic.gdx.graphics.g2d.GlyphLayout +import com.badlogic.gdx.graphics.g2d.PixmapPacker +import com.badlogic.gdx.graphics.g2d.TextureRegion +import com.badlogic.gdx.utils.Array +import com.badlogic.gdx.utils.Disposable import com.unciv.UncivGame -import core.java.nativefont.NativeFont -import core.java.nativefont.NativeFontPaint -object Fonts { - // caches for memory and time saving - private val characterSetCache = HashMap() - private val fontCache = HashMap() +interface NativeFontImplementation { + fun getFontSize(): Int + fun getCharPixmap(char: Char): Pixmap +} - private fun getCharactersForFont(language: String = ""): String { - if (characterSetCache.containsKey(language)) return characterSetCache[language]!! +// This class is loosely based on libgdx's FreeTypeBitmapFontData +class NativeBitmapFontData(val fontImplementation: NativeFontImplementation) : BitmapFontData(), Disposable { + val regions: Array - val startTime = System.currentTimeMillis() + private var dirty = false + private val packer: PixmapPacker - // Basic Character Set - symbols missing from this will not be displayed, unless one of the - // 'complex' languages is chosen, in which case the translation file is used as character set as well. - // This means that for example user-set city names might not be displayed as entered. - // Missing Characters will be entirely invisible, not even take up horizontal space. - // Note that " (normal double quotes) and _ (underscore) _are_ such invisible characters. - val defaultText = - "AÀÁÀÄĂÂEÈÉÊĚÉÈIÌÍÏÍÎOÒÓÖÔÓÖƠUÙÚÜƯŮÚÜ" + // Latin uppercase vowels and similar symbols - "aäàâăäâąáeéèêęěèiìîìíoòöôöơóuùüưůûú" + // Latin lowercase vowels and similar symbols - "BCČĆDĐĎFGHJKLŁĹĽMNPQRŘŔSŠŚTŤVWXYÝZŽŻŹ" + // Latin uppercase consonants and similar symbols - "bcčćçdđďfghjklłĺľmnńňñpqrřŕsșšśtțťvwxyýzžżź" + // Latin lowercase consonants and similar symbols - "АБВГҐДЂЕЁЄЖЗЅИІЇЙЈКЛЉМНЊОПРСТЋУЎФХЦЧЏШЩЪЫЬЭЮЯабвгґдђеёєжзѕиіїйјклљмнњопрстћуўфхцчџшщъыьэюя" + // Russian - "ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψωάßΆέΈέΉίϊΐΊόΌύΰϋΎΫΏ" + // Greek - "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู฿เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛" + // Thai - "İıÇŞşĞğ" + // Turkish - "øæå" + // Scandinavian - "ÃÕãõ" + // Portuguese - "şţșț" + // Romanian - "1234567890" + - "‘?ʼ’'“!”(%)[#]{@}/&\\<-+÷×=>®©\$€£¥¢:;,.…¡*|«»—∞✘✔" - val charSet = HashSet() - charSet.addAll(defaultText.asIterable()) + private val filter = Texture.TextureFilter.Linear - if (language != "") { - for (entry in UncivGame.Current.translations.entries) { - for (lang in entry.value) { - if (lang.key == language) charSet.addAll(lang.value.asIterable()) - } - } - } - val characterSetString = charSet.joinToString("") - characterSetCache[language] = characterSetString + init { + // set general font data + flipped = false + lineHeight = fontImplementation.getFontSize().toFloat() + capHeight = lineHeight + ascent = -lineHeight + down = -lineHeight - val totalTime = System.currentTimeMillis() - startTime - println("Loading characters for font - " + totalTime + "ms") + // Create a packer. + val size = 1024 + val packStrategy = PixmapPacker.GuillotineStrategy() + packer = PixmapPacker(size, size, Pixmap.Format.RGBA8888, 1, false, packStrategy) + packer.transparentColor = Color.WHITE + packer.transparentColor.a = 0f - return characterSetString + // Generate texture regions. + regions = Array() + packer.updateTextureRegions(regions, filter, filter, false) + + // Set space glyph. + val spaceGlyph = getGlyph(' ') + spaceXadvance = spaceGlyph.xadvance.toFloat() } - fun getFont(size: Int): BitmapFont { - val language = UncivGame.Current.settings.language - val fontForLanguage = "Nativefont" - val isUniqueFont = language.contains("Chinese") || language == "Korean" || language == "Japanese" - val keyForFont = if (!isUniqueFont) "$fontForLanguage $size" else "$fontForLanguage $size $language" - if (fontCache.containsKey(keyForFont)) return fontCache[keyForFont]!! + override fun getGlyph(ch: Char): Glyph { + var glyph: Glyph? = super.getGlyph(ch) + if (glyph == null) { + val charPixmap = fontImplementation.getCharPixmap(ch) - val font = NativeFont(NativeFontPaint(size)) - val charsForFont = getCharactersForFont(if (isUniqueFont) language else "") + glyph = Glyph() + glyph.id = ch.toInt() + glyph.width = charPixmap.width + glyph.height = charPixmap.height + glyph.xadvance = glyph.width + val rect = packer.pack(charPixmap) + charPixmap.dispose() + glyph.page = packer.pages.size - 1 // Glyph is always packed into the last page for now. + glyph.srcX = rect.x.toInt() + glyph.srcY = rect.y.toInt() - font.appendText(charsForFont) + // If a page was added, create a new texture region for the incrementally added glyph. + if (regions.size <= glyph.page) + packer.updateTextureRegions(regions, filter, filter, false) + setGlyphRegion(glyph, regions.get(glyph.page)) + setGlyph(ch.toInt(), glyph) + dirty = true + } + return glyph + } - fontCache[keyForFont] = font - return font + override fun getGlyphs(run: GlyphLayout.GlyphRun, str: CharSequence, start: Int, end: Int, lastGlyph: Glyph?) { + packer.packToTexture = true // All glyphs added after this are packed directly to the texture. + super.getGlyphs(run, str, start, end, lastGlyph) + if (dirty) { + dirty = false + packer.updateTextureRegions(regions, filter, filter, false) + } + } + + override fun dispose() { + packer.dispose() + } +} + +object Fonts { + val font by lazy { + val fontData = NativeBitmapFontData(UncivGame.Current.fontImplementation!!) + val font = BitmapFont(fontData, fontData.regions, false) + font.setOwnsTexture(true) + font } } diff --git a/core/src/com/unciv/ui/worldscreen/mainmenu/WorldScreenOptionsPopup.kt b/core/src/com/unciv/ui/worldscreen/mainmenu/WorldScreenOptionsPopup.kt index a8b76d94fb..48052b3e24 100644 --- a/core/src/com/unciv/ui/worldscreen/mainmenu/WorldScreenOptionsPopup.kt +++ b/core/src/com/unciv/ui/worldscreen/mainmenu/WorldScreenOptionsPopup.kt @@ -1,12 +1,10 @@ package com.unciv.ui.worldscreen.mainmenu -import com.unciv.ui.utils.AutoScrollPane as ScrollPane import com.badlogic.gdx.Application import com.badlogic.gdx.Gdx import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.ui.* import com.badlogic.gdx.utils.Array -import com.unciv.UncivGame import com.unciv.logic.civilization.PlayerType import com.unciv.models.UncivSound import com.unciv.models.translations.TranslationFileWriter @@ -16,6 +14,7 @@ import com.unciv.ui.utils.* import com.unciv.ui.worldscreen.WorldScreen import java.util.* import kotlin.concurrent.thread +import com.unciv.ui.utils.AutoScrollPane as ScrollPane class Language(val language:String, val percentComplete:Int){ override fun toString(): String { @@ -339,7 +338,6 @@ class WorldScreenOptionsPopup(val worldScreen:WorldScreen) : Popup(worldScreen) private fun selectLanguage() { settings.language = selectedLanguage worldScreen.game.translations.tryReadTranslationForCurrentLanguage() - CameraStageBaseScreen.resetFonts() // to load chinese characters if necessary reloadWorldAndOptions() } diff --git a/core/src/core/java/nativefont/NativeFont.kt b/core/src/core/java/nativefont/NativeFont.kt deleted file mode 100644 index 744f6d32bd..0000000000 --- a/core/src/core/java/nativefont/NativeFont.kt +++ /dev/null @@ -1,277 +0,0 @@ -package core.java.nativefont - -import com.badlogic.gdx.Application -import com.badlogic.gdx.Gdx -import com.badlogic.gdx.graphics.Color -import com.badlogic.gdx.graphics.Pixmap -import com.badlogic.gdx.graphics.Texture.TextureFilter -import com.badlogic.gdx.graphics.g2d.BitmapFont -import com.badlogic.gdx.graphics.g2d.PixmapPacker -import com.badlogic.gdx.graphics.g2d.TextureRegion -import com.badlogic.gdx.utils.Array -import com.badlogic.gdx.utils.GdxRuntimeException -import java.util.* - -/** - * Created by tian on 2016/10/2. - */ -class NativeFont @JvmOverloads constructor(paint: NativeFontPaint = NativeFontPaint()) : BitmapFont(BitmapFontData(), TextureRegion(), false) { - private var charSet: MutableSet? - private val emojiSet: HashMap - private val magFilter: TextureFilter - private val minFilter: TextureFilter - private var packer: PixmapPacker? - private var pageWidth = 512 - private var paint: NativeFontPaint? - private var size = 0 - - inner class EmojiDate(var path: String, var size: Int) - - private fun createListener() { - var className = "core.java.nativefont.NativeFont" - when (Gdx.app.type) { - Application.ApplicationType.Desktop -> { - className += "Desktop" - } - Application.ApplicationType.Android -> { - className += "Android" - } - Application.ApplicationType.iOS -> { - className += if (robovm) "IOS" else "IOSMoe" - } - Application.ApplicationType.WebGL -> { - className += "Html" - } - } - listener = try { - val claz = Gdx.app.javaClass.classLoader.loadClass(className) as Class - claz.newInstance() - } catch (e: Exception) { - throw GdxRuntimeException("Class Not Found:" + e.message) - } - } - - fun updataSize(newSize: Int) { - size = Math.max(newSize, size) - this.data.down = (-size).toFloat() - this.data.ascent = (-size).toFloat() - this.data.capHeight = size.toFloat() - this.data.lineHeight = size.toFloat() - } - - fun setTextColor(color: Color?): NativeFont { - paint!!.color = color - return this - } - - fun setStrokeColor(color: Color?): NativeFont { - paint!!.strokeColor = color - return this - } - - fun setStrokeWidth(width: Int): NativeFont { - paint!!.strokeWidth = width - return this - } - - fun setSize(size: Int): NativeFont { - paint!!.textSize = size - return this - } - - fun setBold(istrue: Boolean): NativeFont { - paint!!.fakeBoldText = istrue - return this - } - - fun setUnderline(istrue: Boolean): NativeFont { - paint!!.underlineText = istrue - return this - } - - fun setStrikeThru(istrue: Boolean): NativeFont { - paint!!.strikeThruText = istrue - return this - } - - fun setPaint(paint: NativeFontPaint?): NativeFont { - this.paint = paint - return this - } - - fun addEmojiPath(emojiKey: String?, imgPath: String, size: Int): NativeFont { - emojiSet[emojiKey] = EmojiDate(imgPath, size) - return this - } - - fun appendEmoji(txt: String, imgname: String?, size: Int): NativeFont { - val pixmap = Pixmap(Gdx.files.internal(imgname)) - // Pixmap.setFilter(Pixmap.Filter.BiLinear); - val pixmap2 = Pixmap(size, size, Pixmap.Format.RGBA8888) - pixmap2.filter = Pixmap.Filter.BiLinear - pixmap2.drawPixmap(pixmap, 0, 0, pixmap.width, pixmap.height, 0, 0, size, size) - pixmap.dispose() - appendEmoji(txt, pixmap2) - return this - } - - fun appendEmoji(txt: String, pixmap: Pixmap): NativeFont { - if (charSet!!.add(txt)) { - if (packer == null) { - packer = PixmapPacker(pageWidth, pageWidth, Pixmap.Format.RGBA8888, 2, false) - } - putGlyph(txt[0], pixmap) - updataSize(pixmap.height) - upData() - } - return this - } - - fun createText(characters: String?): NativeFont { - if (!(characters == null || characters.length == 0)) { - create(characters, true) - end() - } - return this - } - - fun appendText(characters: String?): NativeFont { - if (!(characters == null || characters.length == 0)) { - create(characters, false) - } - return this - } - - private fun create(characters: String, haveMinPageSize: Boolean) { - var characters = characters - characters = characters.replace("[\\t\\n\\x0B\\f\\r]".toRegex(), "") - val arrayOfCharsAsStrings = Array() - for (c2 in characters.toCharArray()) { - if (charSet!!.add(c2.toString())) { - arrayOfCharsAsStrings.add(c2.toString()) - } - } - if (haveMinPageSize) { - pageWidth = (paint!!.textSize + 2) * (Math.sqrt(arrayOfCharsAsStrings.size.toDouble()) + 1.0).toInt() - } - if (packer == null) { - packer = PixmapPacker(pageWidth, pageWidth, Pixmap.Format.RGBA8888, 2, false) - } - - val putGlyphStartTime = System.currentTimeMillis() - for (i in 0 until arrayOfCharsAsStrings.size) { - val txt = arrayOfCharsAsStrings[i] - val c2 = txt[0] - val css = c2.toString() - if (emojiSet[css] != null) { - charSet!!.remove(css) - val date = emojiSet[css] - appendEmoji(c2.toString() + "", date!!.path, date.size) - } else { - putGlyph(c2, listener!!.getFontPixmap(txt, paint!!)) - } - } - - val putGlyphTime = System.currentTimeMillis() - putGlyphStartTime - println("Putting glyphs - "+putGlyphTime+"ms") - - updataSize(size) - upData() - - if (regions.size == 1) { - setOwnsTexture(true) - } else { - setOwnsTexture(false) - } - } - - private fun putGlyph(c: Char, pixmap: Pixmap) { - val rect = packer!!.pack(c.toString(), pixmap) - pixmap.dispose() - val pIndex = packer!!.getPageIndex(c.toString()) - val glyph = Glyph() - glyph.id = c.toInt() - glyph.page = pIndex - glyph.srcX = rect.x.toInt() - glyph.srcY = rect.y.toInt() - glyph.width = rect.width.toInt() - glyph.height = rect.height.toInt() - glyph.xadvance = glyph.width - this.data!!.setGlyph(c.toInt(), glyph) - } - - private fun upData() { - var spaceGlyph = this.data!!.getGlyph(' ') - if (spaceGlyph == null) { - spaceGlyph = Glyph() - var xadvanceGlyph = this.data!!.getGlyph('l') - if (xadvanceGlyph == null) { - xadvanceGlyph = this.data!!.firstGlyph - } - spaceGlyph.xadvance = xadvanceGlyph!!.xadvance - spaceGlyph.id = 32 - this.data!!.setGlyph(32, spaceGlyph) - } - this.data!!.spaceXadvance = (spaceGlyph.xadvance + spaceGlyph.width).toFloat() - val pages = packer!!.pages - val regions = regions - val regSize = regions.size - 1 - for (i in 0 until pages.size) { - val p = pages[i] - if (i > regSize) { - p.updateTexture(minFilter, magFilter, false) - regions.add(TextureRegion(p.texture)) - } else { - if (p.updateTexture(minFilter, magFilter, false)) { - regions[i] = TextureRegion(p.texture) - } - } - } - for (page in this.data!!.glyphs) { - if (page == null) continue - for (glyph in page) { - if (glyph != null) { - val region = getRegions()[glyph.page] - ?: throw IllegalArgumentException("BitmapFont texture region array cannot contain null elements.") - this.data!!.setGlyphRegion(glyph, region) - } - } - } - } - - fun end(): NativeFont { - paint = null - charSet!!.clear() - charSet = null - packer!!.dispose() - packer = null - return this - } - - override fun dispose() { - end() - super.dispose() - } - - companion object { - var listener: NativeFontListener? = null - private set - private var robovm = false - fun setRobovm() { - robovm = true - } - - } - - init { - this.paint = NativeFontPaint() - charSet = HashSet() - packer = null - minFilter = TextureFilter.Linear - magFilter = TextureFilter.Linear - emojiSet = HashMap() - updataSize(paint.textSize) - if (listener == null) createListener() - this.paint = paint - } -} \ No newline at end of file diff --git a/core/src/core/java/nativefont/NativeFontListener.kt b/core/src/core/java/nativefont/NativeFontListener.kt deleted file mode 100755 index 33b9fb29f7..0000000000 --- a/core/src/core/java/nativefont/NativeFontListener.kt +++ /dev/null @@ -1,10 +0,0 @@ -package core.java.nativefont - -import com.badlogic.gdx.graphics.Pixmap - -/** - * Created by tian on 2016/10/2. - */ -interface NativeFontListener { - fun getFontPixmap(txt: String, vpaint: NativeFontPaint): Pixmap -} \ No newline at end of file diff --git a/core/src/core/java/nativefont/NativeFontPaint.kt b/core/src/core/java/nativefont/NativeFontPaint.kt deleted file mode 100755 index e234fa20f3..0000000000 --- a/core/src/core/java/nativefont/NativeFontPaint.kt +++ /dev/null @@ -1,79 +0,0 @@ -package core.java.nativefont - -import com.badlogic.gdx.graphics.Color - -/** - * Created by tian on 2016/10/2. - */ -class NativeFontPaint { - var textSize = 30 // 字号 - var color = Color.WHITE // 颜色 - var fakeBoldText = false // 是否粗体 - var underlineText = false // 是否下划线 - var strikeThruText = false // 是否删除线 - var strokeColor: Color? = null // 描边颜色 - var strokeWidth = 3 // 描边宽度 - var tTFName = "" - val name: String - get() { - val name = StringBuffer() - name.append(tTFName).append("_").append(textSize).append("_").append(color.toIntBits()) - .append("_").append(booleanToInt(fakeBoldText)).append("_") - .append(booleanToInt(underlineText)) - if (strokeColor != null) { - name.append("_").append(strokeColor!!.toIntBits()).append("_").append(strokeWidth) - } - return name.toString() - } - - private fun booleanToInt(b: Boolean): Int { - return if (b == true) 0 else 1 - } - - constructor() {} - constructor(ttfName: String, textSize: Int, color: Color, stroke: Color?, strokeWidth: Int, - bold: Boolean, line: Boolean, thru: Boolean) { - tTFName = ttfName - this.textSize = textSize - this.color = color - strokeColor = stroke - this.strokeWidth = strokeWidth - fakeBoldText = bold - underlineText = line - strikeThruText = thru - } - - constructor(ttfName: String) { - tTFName = ttfName - } - - constructor(ttfName: String, size: Int) { - tTFName = ttfName - textSize = size - } - - constructor(ttfName: String, size: Int, color: Color) { - tTFName = ttfName - textSize = size - this.color = color - } - - constructor(ttfName: String, color: Color) { - tTFName = ttfName - this.color = color - } - - constructor(size: Int) { - textSize = size - } - - constructor(color: Color) { - this.color = color - } - - constructor(size: Int, color: Color) { - textSize = size - this.color = color - } - -} \ No newline at end of file diff --git a/desktop/src/com/unciv/app/desktop/DesktopLauncher.kt b/desktop/src/com/unciv/app/desktop/DesktopLauncher.kt index bb0b505a86..0f0220a056 100644 --- a/desktop/src/com/unciv/app/desktop/DesktopLauncher.kt +++ b/desktop/src/com/unciv/app/desktop/DesktopLauncher.kt @@ -15,7 +15,6 @@ import java.util.* import kotlin.concurrent.timer import kotlin.system.exitProcess - internal object DesktopLauncher { private var discordTimer: Timer? = null @@ -32,7 +31,7 @@ internal object DesktopLauncher { val versionFromJar = DesktopLauncher.javaClass.`package`.specificationVersion ?: "Desktop" - val game = UncivGame ( versionFromJar, null, { exitProcess(0) }, { discordTimer?.cancel() } ) + val game = UncivGame ( versionFromJar, null, { exitProcess(0) }, { discordTimer?.cancel() }, NativeFontDesktop(45) ) if(!RaspberryPiDetector.isRaspberryPi()) // No discord RPC for Raspberry Pi, see https://github.com/yairm210/Unciv/issues/1624 tryActivateDiscord(game) diff --git a/desktop/src/com/unciv/app/desktop/NativeFontDesktop.kt b/desktop/src/com/unciv/app/desktop/NativeFontDesktop.kt new file mode 100755 index 0000000000..68d133115f --- /dev/null +++ b/desktop/src/com/unciv/app/desktop/NativeFontDesktop.kt @@ -0,0 +1,47 @@ +package com.unciv.app.desktop + +import com.badlogic.gdx.graphics.Pixmap +import com.unciv.ui.utils.NativeFontImplementation +import java.awt.* +import java.awt.image.BufferedImage + + +class NativeFontDesktop(val size: Int) : NativeFontImplementation { + private val font by lazy { + Font("", Font.PLAIN, size) + } + private val metric by lazy { + val bi = BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR) + val g = bi.createGraphics() + g.font = font + g.fontMetrics!! + } + + override fun getFontSize(): Int { + return size + } + + override fun getCharPixmap(char: Char): Pixmap { + var width = metric.charWidth(char) + var height = metric.ascent + metric.descent + if (width == 0) { + height = size + width = height + } + val bi = BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR) + val g = bi.createGraphics() + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) + g.font = font + g.color = Color.WHITE + g.drawString(char.toString(), 0, metric.ascent) + val pixmap = Pixmap(bi.width, bi.height, Pixmap.Format.RGBA8888) + val data = bi.getRGB(0, 0, bi.width, bi.height, null, 0, bi.width) + for (i in 0 until bi.width) { + for (j in 0 until bi.height) { + pixmap.setColor(Integer.reverseBytes(data[i + (j * bi.width)])) + pixmap.drawPixel(i, j) + } + } + return pixmap + } +} \ No newline at end of file diff --git a/desktop/src/core/java/nativefont/NativeFontDesktop.kt b/desktop/src/core/java/nativefont/NativeFontDesktop.kt deleted file mode 100755 index c2608315d5..0000000000 --- a/desktop/src/core/java/nativefont/NativeFontDesktop.kt +++ /dev/null @@ -1,104 +0,0 @@ -package core.java.nativefont - -import com.badlogic.gdx.Gdx -import com.badlogic.gdx.graphics.Pixmap -import java.awt.* -import java.awt.font.TextAttribute -import java.awt.image.BufferedImage -import java.io.BufferedInputStream -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.io.IOException -import java.text.AttributedString -import java.util.* -import javax.imageio.ImageIO -import javax.swing.UIManager - -class NativeFontDesktop : NativeFontListener { - private val fonts = HashMap() - private val metrics = HashMap() - override fun getFontPixmap(txt: String, vpaint: NativeFontPaint): Pixmap { - val font = getFont(vpaint) - val fm = metrics[vpaint.name] - var strWidth = fm!!.stringWidth(txt) - var strHeight = fm.ascent + fm.descent - if (strWidth == 0) { - strHeight = vpaint.textSize - strWidth = strHeight - } - val bi = BufferedImage(strWidth, strHeight, BufferedImage.TYPE_4BYTE_ABGR) - val g = bi.createGraphics() - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) - g.font = font - when { - vpaint.strokeColor != null -> { // 描边 - val v = font!!.createGlyphVector(fm.fontRenderContext, txt) - val shape = v.outline - g.color = UIManager.getColor(vpaint.color) - g.translate(0, fm.ascent) - g.fill(shape) - g.stroke = BasicStroke(vpaint.strokeWidth.toFloat()) - g.color = UIManager.getColor(vpaint.strokeColor) - g.draw(shape) - } - vpaint.underlineText -> { // 下划线 - val `as` = AttributedString(txt) - `as`.addAttribute(TextAttribute.FONT, font) - `as`.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON) - g.color = UIManager.getColor(vpaint.color) - g.drawString(`as`.iterator, 0, fm.ascent) - } - vpaint.strikeThruText -> { // 删除线 - val `as` = AttributedString(txt) - `as`.addAttribute(TextAttribute.FONT, font) - `as`.addAttribute(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON) - g.color = UIManager.getColor(vpaint.color) - g.drawString(`as`.iterator, 0, fm.ascent) - } - else -> { // 普通 - g.color = UIManager.getColor(vpaint.color) - g.drawString(txt, 0, fm.ascent) - } - } - lateinit var pixmap: Pixmap - try { - val buffer = ByteArrayOutputStream() - ImageIO.write(bi, "png", buffer) - pixmap = Pixmap(buffer.toByteArray(), 0, buffer.toByteArray().size) - buffer.close() - } catch (e: IOException) { - e.printStackTrace() - } - return pixmap - } - - private fun getFont(vpaint: NativeFontPaint): Font? { - val isBolo = vpaint.fakeBoldText || vpaint.strokeColor != null - var font = fonts[vpaint.name] - if (font == null) { - if (vpaint.tTFName == "") { - font = Font("", if (isBolo) Font.BOLD else Font.PLAIN, vpaint.textSize) - } else { - try { - val `in` = ByteArrayInputStream(Gdx.files.internal(vpaint.tTFName + - if (vpaint.tTFName.endsWith(".ttf")) "" else ".ttf").readBytes()) - val fb = BufferedInputStream(`in`) - font = Font.createFont(Font.TRUETYPE_FONT, fb).deriveFont(Font.BOLD, vpaint.textSize.toFloat()) - fb.close() - `in`.close() - } catch (e: FontFormatException) { - e.printStackTrace() - } catch (e: IOException) { - e.printStackTrace() - } - } - fonts[vpaint.name] = font - val bi = BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR) - val g = bi.createGraphics() - g.font = font - val fm = g.fontMetrics - metrics[vpaint.name] = fm - } - return font - } -} \ No newline at end of file