mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-27 05:46:43 -04:00
Rewrite the font system to render the characters incrementally (#2635)
This commit is contained in:
parent
7330daff48
commit
4b6bab523f
@ -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)
|
||||
}
|
||||
|
45
android/src/com/unciv/app/NativeFontAndroid.kt
Executable file
45
android/src/com/unciv/app/NativeFontAndroid.kt
Executable file
@ -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
|
||||
}
|
||||
}
|
@ -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<String, Typeface>()
|
||||
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()
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
@ -80,7 +80,6 @@ class LanguagePickerScreen(): PickerScreen(){
|
||||
game.settings.save()
|
||||
|
||||
game.translations.tryReadTranslationForCurrentLanguage()
|
||||
resetFonts()
|
||||
game.setScreen(MainMenuScreen())
|
||||
dispose()
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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<String, String>()
|
||||
private val fontCache = HashMap<String, BitmapFont>()
|
||||
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<TextureRegion>
|
||||
|
||||
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<Char>()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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<String?>?
|
||||
private val emojiSet: HashMap<String?, EmojiDate?>
|
||||
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<out NativeFontListener>
|
||||
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<String>()
|
||||
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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
|
47
desktop/src/com/unciv/app/desktop/NativeFontDesktop.kt
Executable file
47
desktop/src/com/unciv/app/desktop/NativeFontDesktop.kt
Executable file
@ -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
|
||||
}
|
||||
}
|
@ -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<String, Font?>()
|
||||
private val metrics = HashMap<String, FontMetrics>()
|
||||
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
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user