Rewrite the font system to render the characters incrementally (#2635)

This commit is contained in:
Väinö Mäkelä 2020-05-18 23:09:38 +03:00 committed by GitHub
parent 7330daff48
commit 4b6bab523f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 193 additions and 614 deletions

View File

@ -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)
}

View 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
}
}

View File

@ -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()
}
}

View File

@ -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()

View File

@ -80,7 +80,6 @@ class LanguagePickerScreen(): PickerScreen(){
game.settings.save()
game.translations.tryReadTranslationForCurrentLanguage()
resetFonts()
game.setScreen(MainMenuScreen())
dispose()
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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()
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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)

View 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
}
}

View File

@ -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
}
}