diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..51eb804
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+demo/out/*
+demo/lib/*
diff --git a/Slick2d/GameFontBase.kt b/Slick2d/GameFontBase.kt
index 72f43c9..0a03dc1 100644
--- a/Slick2d/GameFontBase.kt
+++ b/Slick2d/GameFontBase.kt
@@ -1,9 +1,9 @@
package net.torvald.imagefont
-import net.torvald.terrarum.Terrarum
-import net.torvald.terrarum.getPixel
import org.lwjgl.opengl.GL11
import org.newdawn.slick.*
+import org.newdawn.slick.opengl.Texture
+import java.nio.ByteOrder
import java.util.*
/**
@@ -418,17 +418,38 @@ open class GameFontBase : Font {
drawString(x + xoff, y, printedBody, color)
}
- private fun setBlendModeMul() {
- GL11.glEnable(GL11.GL_BLEND)
- GL11.glBlendFunc(GL11.GL_DST_COLOR, GL11.GL_ONE_MINUS_SRC_ALPHA)
- }
-
- private fun setBlendModeNormal() {
- GL11.glDisable(GL11.GL_BLEND)
- Terrarum.appgc.graphics.setDrawMode(Graphics.MODE_NORMAL)
- }
-
fun buildWidthTable(sheet: SpriteSheet, codeOffset: Int, codeRange: IntRange, rows: Int = 16) {
+ fun Byte.toUint() = java.lang.Byte.toUnsignedInt(this)
+
+ /** @return Intarray(R, G, B, A) */
+ fun Texture.getPixel(x: Int, y: Int): IntArray {
+ val textureWidth = this.textureWidth
+ val hasAlpha = this.hasAlpha()
+
+ val offset = (if (hasAlpha) 4 else 3) * (textureWidth * y + x) // 4: # of channels (RGBA)
+
+ if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) {
+ return intArrayOf(
+ this.textureData[offset].toUint(),
+ this.textureData[offset + 1].toUint(),
+ this.textureData[offset + 2].toUint(),
+ if (hasAlpha)
+ this.textureData[offset + 3].toUint()
+ else 255
+ )
+ }
+ else {
+ return intArrayOf(
+ this.textureData[offset + 2].toUint(),
+ this.textureData[offset + 1].toUint(),
+ this.textureData[offset].toUint(),
+ if (hasAlpha)
+ this.textureData[offset + 3].toUint()
+ else 255
+ )
+ }
+ }
+
val binaryCodeOffset = 15
val cellW = sheet.getSubImage(0, 0).width + 1 // should be 16
diff --git a/Slick2d/GameFontImpl.kt b/Slick2d/GameFontImpl.kt
index ece5d3a..a717267 100644
--- a/Slick2d/GameFontImpl.kt
+++ b/Slick2d/GameFontImpl.kt
@@ -1,5 +1,6 @@
package net.torvald.imagefont
+import net.torvald.terrarum.imagefont.GameFontDemo
import org.newdawn.slick.*
/**
@@ -27,7 +28,11 @@ class GameFontImpl : GameFontBase() {
, W_UNIHAN, H_UNIHAN
);*/
GameFontBase.cyrilic = SpriteSheet(
- "./assets/graphics/fonts/cyrilic_variable.tga", 15, 19, 1)
+ when (GameFontDemo.gameLocale.substring(0..1)) {
+ "bg" -> "./assets/graphics/fonts/cyrilic_bulgarian_variable.tga"
+ "sr" -> "./assets/graphics/fonts/cyrilic_serbian_variable.tga"
+ else -> "./assets/graphics/fonts/cyrilic_variable.tga"
+ }, 15, 19, 1)
GameFontBase.fullwidthForms = SpriteSheet(
"./assets/graphics/fonts/fullwidth_forms.tga", GameFontBase.W_UNIHAN, GameFontBase.H_UNIHAN)
GameFontBase.uniPunct = SpriteSheet(
@@ -57,7 +62,7 @@ class GameFontImpl : GameFontBase() {
GameFontBase.wenQuanYi_2,
GameFontBase.greekSheet,
GameFontBase.romanianSheet,
- GameFontBase.romanianSheetNarrow,
+ GameFontBase.romanianSheetNarrow
)
GameFontBase.sheetKey = shk
diff --git a/demo/.idea/.name b/demo/.idea/.name
new file mode 100644
index 0000000..d28c669
--- /dev/null
+++ b/demo/.idea/.name
@@ -0,0 +1 @@
+TerrarumSansDemo
\ No newline at end of file
diff --git a/demo/.idea/artifacts/unnamed.xml b/demo/.idea/artifacts/unnamed.xml
new file mode 100644
index 0000000..dcc4e25
--- /dev/null
+++ b/demo/.idea/artifacts/unnamed.xml
@@ -0,0 +1,15 @@
+
+
+ $PROJECT_DIR$/out/artifacts/unnamed
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/.idea/libraries/KotlinJavaRuntime.xml b/demo/.idea/libraries/KotlinJavaRuntime.xml
new file mode 100644
index 0000000..a83cacd
--- /dev/null
+++ b/demo/.idea/libraries/KotlinJavaRuntime.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/.idea/misc.xml b/demo/.idea/misc.xml
new file mode 100644
index 0000000..0548357
--- /dev/null
+++ b/demo/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/.idea/modules.xml b/demo/.idea/modules.xml
new file mode 100644
index 0000000..f14e97b
--- /dev/null
+++ b/demo/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/.idea/workspace.xml b/demo/.idea/workspace.xml
new file mode 100644
index 0000000..605e909
--- /dev/null
+++ b/demo/.idea/workspace.xml
@@ -0,0 +1,610 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ "|
+ ,\n
+ main
+
+
+ \n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1487243351068
+
+
+ 1487243351068
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ unnamed
+
+
+
+
+
+
+
+
+
+
+
+
+ Kotlin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1.8
+
+
+
+
+
+
+
+
+
+
+
+ TerrarumSansDemo
+
+
+
+
+
+
+
+
+
+
+
+
+ 1.8
+
+
+
+
+
+
+
+
+
+
+
+ KotlinJavaRuntime
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/META-INF/MANIFEST.MF b/demo/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..908b34f
--- /dev/null
+++ b/demo/META-INF/MANIFEST.MF
@@ -0,0 +1,4 @@
+Manifest-Version: 1.0
+Class-Path: lib/lwjgl.jar lib/lwjgl_util.jar lib/slick.jar lib/kotlin-reflect.jar lib/kotlin-runtime.jar
+Main-Class: net.torvald.terrarum.imagefont.GameFontDemoKt
+
diff --git a/demo/TerrarumSansDemo.iml b/demo/TerrarumSansDemo.iml
new file mode 100644
index 0000000..b19a530
--- /dev/null
+++ b/demo/TerrarumSansDemo.iml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/config.properties b/demo/config.properties
new file mode 100644
index 0000000..c22105c
--- /dev/null
+++ b/demo/config.properties
@@ -0,0 +1 @@
+locale=enUS
\ No newline at end of file
diff --git a/demo/src/net/torvald/terrarum/imagefont/GameFontBase.kt b/demo/src/net/torvald/terrarum/imagefont/GameFontBase.kt
new file mode 100644
index 0000000..0a03dc1
--- /dev/null
+++ b/demo/src/net/torvald/terrarum/imagefont/GameFontBase.kt
@@ -0,0 +1,552 @@
+package net.torvald.imagefont
+
+import org.lwjgl.opengl.GL11
+import org.newdawn.slick.*
+import org.newdawn.slick.opengl.Texture
+import java.nio.ByteOrder
+import java.util.*
+
+/**
+ * Created by minjaesong on 16-01-27.
+ */
+open class GameFontBase : Font {
+
+ private fun getHan(hanIndex: Int): IntArray {
+ val han_x = hanIndex % JONG_COUNT
+ val han_y = hanIndex / JONG_COUNT
+ val ret = intArrayOf(han_x, han_y)
+ return ret
+ }
+
+ private fun getHanChosung(hanIndex: Int) = hanIndex / (JUNG_COUNT * JONG_COUNT)
+
+ private fun getHanJungseong(hanIndex: Int) = hanIndex / JONG_COUNT % JUNG_COUNT
+
+ private fun getHanJongseong(hanIndex: Int) = hanIndex % JONG_COUNT
+
+ private val jungseongWide = arrayOf(8, 12, 13, 17, 18, 21)
+ private val jungseongComplex = arrayOf(9, 10, 11, 14, 15, 16, 22)
+
+ private fun isJungseongWide(hanIndex: Int) = jungseongWide.contains(getHanJungseong(hanIndex))
+ private fun isJungseongComplex(hanIndex: Int) = jungseongComplex.contains(getHanJungseong(hanIndex))
+
+ private fun getHanInitialRow(hanIndex: Int): Int {
+ val ret: Int
+
+ if (isJungseongWide(hanIndex))
+ ret = 2
+ else if (isJungseongComplex(hanIndex))
+ ret = 4
+ else
+ ret = 0
+
+ return if (getHanJongseong(hanIndex) == 0) ret else ret + 1
+ }
+
+ private fun getHanMedialRow(hanIndex: Int) = if (getHanJongseong(hanIndex) == 0) 6 else 7
+
+ private fun getHanFinalRow(hanIndex: Int): Int {
+ val jungseongIndex = getHanJungseong(hanIndex)
+
+ return if (jungseongWide.contains(jungseongIndex))
+ 8
+ else
+ 9
+ }
+
+ private fun isHangul(c: Char) = c.toInt() >= 0xAC00 && c.toInt() < 0xD7A4
+ private fun isAscii(c: Char) = c.toInt() >= 0x20 && c.toInt() <= 0xFF
+ private fun isExtA(c: Char) = c.toInt() >= 0x100 && c.toInt() < 0x180
+ private fun isKana(c: Char) = c.toInt() >= 0x3040 && c.toInt() < 0x3100
+ private fun isCJKPunct(c: Char) = c.toInt() >= 0x3000 && c.toInt() < 0x3040
+ private fun isUniHan(c: Char) = c.toInt() >= 0x3400 && c.toInt() < 0xA000
+ private fun isCyrilic(c: Char) = c.toInt() >= 0x400 && c.toInt() < 0x460
+ private fun isFullwidthUni(c: Char) = c.toInt() >= 0xFF00 && c.toInt() < 0xFF20
+ private fun isUniPunct(c: Char) = c.toInt() >= 0x2000 && c.toInt() < 0x2070
+ private fun isWenQuanYi1(c: Char) = c.toInt() >= 0x33F3 && c.toInt() <= 0x69FC
+ private fun isWenQuanYi2(c: Char) = c.toInt() >= 0x69FD && c.toInt() <= 0x9FDC
+ private fun isGreek(c: Char) = c.toInt() >= 0x370 && c.toInt() <= 0x3CE
+ private fun isRomanian(c: Char) = c.toInt() >= 0x218 && c.toInt() <= 0x21A
+ private fun isRomanianNarrow(c: Char) = c.toInt() == 0x21B
+
+
+
+ private fun extAindexX(c: Char) = (c.toInt() - 0x100) % 16
+ private fun extAindexY(c: Char) = (c.toInt() - 0x100) / 16
+
+ private fun kanaIndexX(c: Char) = (c.toInt() - 0x3040) % 16
+ private fun kanaIndexY(c: Char) = (c.toInt() - 0x3040) / 16
+
+ private fun cjkPunctIndexX(c: Char) = (c.toInt() - 0x3000) % 16
+ private fun cjkPunctIndexY(c: Char) = (c.toInt() - 0x3000) / 16
+
+ private fun uniHanIndexX(c: Char) = (c.toInt() - 0x3400) % 256
+ private fun uniHanIndexY(c: Char) = (c.toInt() - 0x3400) / 256
+
+ private fun cyrilicIndexX(c: Char) = (c.toInt() - 0x400) % 16
+ private fun cyrilicIndexY(c: Char) = (c.toInt() - 0x400) / 16
+
+ private fun fullwidthUniIndexX(c: Char) = (c.toInt() - 0xFF00) % 16
+ private fun fullwidthUniIndexY(c: Char) = (c.toInt() - 0xFF00) / 16
+
+ private fun uniPunctIndexX(c: Char) = (c.toInt() - 0x2000) % 16
+ private fun uniPunctIndexY(c: Char) = (c.toInt() - 0x2000) / 16
+
+ private fun wenQuanYiIndexX(c: Char) =
+ (c.toInt() - if (c.toInt() <= 0x4DB5) 0x33F3 else 0x33F3 + 0x4A) % 32
+ private fun wenQuanYi1IndexY(c: Char) = (c.toInt() - (0x33F3 + 0x4A)) / 32
+ private fun wenQuanYi2IndexY(c: Char) = (c.toInt() - 0x69FD) / 32
+
+ private fun greekIndexX(c: Char) = (c.toInt() - 0x370) % 16
+ private fun greekIndexY(c: Char) = (c.toInt() - 0x370) / 16
+
+ private fun romanianIndexX(c: Char) = c.toInt() - 0x218
+ private fun romanianIndexY(c: Char) = 0
+
+ private fun thaiIndexX(c: Char) = (c.toInt() - 0xE00) % 16
+ private fun thaiIndexY(c: Char) = (c.toInt() - 0xE00) / 16
+
+ private fun thaiNarrowIndexX(c: Char) = 3
+ private fun thaiNarrowIndexY(c: Char) = 0
+
+
+ private val narrowWidthSheets = arrayOf(
+ SHEET_EXTB_ROMANIAN_NARROW
+ )
+ private val unihanWidthSheets = arrayOf(
+ SHEET_UNIHAN,
+ SHEET_FW_UNI,
+ SHEET_WENQUANYI_1,
+ SHEET_WENQUANYI_2
+ )
+ private val zeroWidthSheets = arrayOf(
+ SHEET_COLOURCODE
+ )
+ private val variableWidthSheets = arrayOf(
+ SHEET_ASCII_VARW,
+ SHEET_CYRILIC_VARW,
+ SHEET_EXTA_VARW,
+ SHEET_GREEK_VARW
+ )
+
+
+ override fun getWidth(s: String) = getWidthSubstr(s, s.length)
+
+ private fun getWidthSubstr(s: String, endIndex: Int): Int {
+ var len = 0
+ for (i in 0..endIndex - 1) {
+ val chr = s[i]
+ val ctype = getSheetType(s[i])
+
+ if (chr.toInt() == 0x21B) // Romanian t; HAX!
+ len += 6
+ else if (variableWidthSheets.contains(ctype)) {
+ try {
+ len += asciiWidths[chr.toInt()]!!
+ }
+ catch (e: kotlin.KotlinNullPointerException) {
+ println("KotlinNullPointerException on glyph number ${Integer.toHexString(chr.toInt()).toUpperCase()}")
+ System.exit(1)
+ }
+ }
+ else if (zeroWidthSheets.contains(ctype))
+ len += 0
+ else if (narrowWidthSheets.contains(ctype))
+ len += W_LATIN_NARROW
+ else if (ctype == SHEET_CJK_PUNCT)
+ len += W_ASIAN_PUNCT
+ else if (ctype == SHEET_HANGUL)
+ len += W_HANGUL
+ else if (ctype == SHEET_KANA)
+ len += W_KANA
+ else if (unihanWidthSheets.contains(ctype))
+ len += W_UNIHAN
+ else
+ len += W_LATIN_WIDE
+
+ if (i < endIndex - 1) len += interchar
+ }
+ return len * scale
+ }
+
+ override fun getHeight(s: String) = H * scale
+
+ override fun getLineHeight() = H * scale
+
+ override fun drawString(x: Float, y: Float, s: String) = drawString(x, y, s, Color.white)
+
+ override fun drawString(x: Float, y: Float, s: String, color: Color) {
+ GL11.glEnable(GL11.GL_BLEND)
+ GL11.glColorMask(true, true, true, true)
+ GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA)
+
+ var thisCol = color
+
+ // hangul fonts first
+ //hangulSheet.startUse() // disabling texture binding to make the font coloured
+ // JOHAB
+ for (i in 0..s.length - 1) {
+ val ch = s[i]
+
+ if (isHangul(ch)) {
+ val hIndex = ch.toInt() - 0xAC00
+
+ val indexCho = getHanChosung(hIndex)
+ val indexJung = getHanJungseong(hIndex)
+ val indexJong = getHanJongseong(hIndex)
+
+ val choRow = getHanInitialRow(hIndex)
+ val jungRow = getHanMedialRow(hIndex)
+ val jongRow = getHanFinalRow(hIndex)
+
+ val glyphW = getWidth(ch.toString())
+
+ hangulSheet.getSubImage(indexCho, choRow).drawWithShadow(
+ Math.round(x + getWidthSubstr(s, i + 1) - glyphW).toFloat(),
+ Math.round(((H - H_HANGUL) / 2).toFloat() + y + 1f).toFloat(),
+ scale.toFloat(), thisCol
+ )
+ hangulSheet.getSubImage(indexJung, jungRow).drawWithShadow(
+ Math.round(x + getWidthSubstr(s, i + 1) - glyphW).toFloat(),
+ Math.round(((H - H_HANGUL) / 2).toFloat() + y + 1f).toFloat(),
+ scale.toFloat(), thisCol
+ )
+ hangulSheet.getSubImage(indexJong, jongRow).drawWithShadow(
+ Math.round(x + getWidthSubstr(s, i + 1) - glyphW).toFloat(),
+ Math.round(((H - H_HANGUL) / 2).toFloat() + y + 1f).toFloat(),
+ scale.toFloat(), thisCol
+ )
+ }
+ }
+ //hangulSheet.endUse()
+
+ // unihan fonts
+ /*uniHan.startUse();
+ for (int i = 0; i < s.length(); i++) {
+ char ch = s.charAt(i);
+
+ if (isUniHan(ch)) {
+ int glyphW = getWidth("" + ch);
+ uniHan.renderInUse(
+ Math.round(x
+ + getWidthSubstr(s, i + 1) - glyphW
+ )
+ , Math.round((H - H_UNIHAN) / 2 + y)
+ , uniHanIndexX(ch)
+ , uniHanIndexY(ch)
+ );
+ }
+ }
+
+ uniHan.endUse();*/
+
+ // WenQuanYi 1
+ //wenQuanYi_1.startUse()
+
+ for (i in 0..s.length - 1) {
+ val ch = s[i]
+
+ if (isWenQuanYi1(ch)) {
+ val glyphW = getWidth("" + ch)
+ wenQuanYi_1.getSubImage(wenQuanYiIndexX(ch), wenQuanYi1IndexY(ch)).drawWithShadow(
+ Math.round(x + getWidthSubstr(s, i + 1) - glyphW).toFloat(),
+ Math.round((H - H_UNIHAN) / 2 + y).toFloat(),
+ scale.toFloat(), thisCol
+ )
+ }
+ }
+
+ //wenQuanYi_1.endUse()
+ // WenQuanYi 2
+ //wenQuanYi_2.startUse()
+
+ for (i in 0..s.length - 1) {
+ val ch = s[i]
+
+ if (isWenQuanYi2(ch)) {
+ val glyphW = getWidth("" + ch)
+ wenQuanYi_2.getSubImage(wenQuanYiIndexX(ch), wenQuanYi2IndexY(ch)).drawWithShadow(
+ Math.round(x + getWidthSubstr(s, i + 1) - glyphW).toFloat(),
+ Math.round((H - H_UNIHAN) / 2 + y).toFloat(),
+ scale.toFloat(), thisCol
+ )
+ }
+ }
+
+ //wenQuanYi_2.endUse()
+
+ // regular fonts
+ var prevInstance = -1
+ for (i in 0..s.length - 1) {
+ val ch = s[i]
+
+ if (!isHangul(ch) && !isUniHan(ch)) {
+
+ // if not init, endUse first
+ if (prevInstance != -1) {
+ //sheetKey[prevInstance].endUse()
+ }
+ //sheetKey[getSheetType(ch)].startUse()
+ prevInstance = getSheetType(ch)
+
+ val sheetX: Int
+ val sheetY: Int
+ when (prevInstance) {
+ SHEET_EXTA_VARW -> {
+ sheetX = extAindexX(ch)
+ sheetY = extAindexY(ch)
+ }
+ SHEET_KANA -> {
+ sheetX = kanaIndexX(ch)
+ sheetY = kanaIndexY(ch)
+ }
+ SHEET_CJK_PUNCT -> {
+ sheetX = cjkPunctIndexX(ch)
+ sheetY = cjkPunctIndexY(ch)
+ }
+ SHEET_CYRILIC_VARW -> {
+ sheetX = cyrilicIndexX(ch)
+ sheetY = cyrilicIndexY(ch)
+ }
+ SHEET_FW_UNI -> {
+ sheetX = fullwidthUniIndexX(ch)
+ sheetY = fullwidthUniIndexY(ch)
+ }
+ SHEET_UNI_PUNCT -> {
+ sheetX = uniPunctIndexX(ch)
+ sheetY = uniPunctIndexY(ch)
+ }
+ SHEET_GREEK_VARW -> {
+ sheetX = greekIndexX(ch)
+ sheetY = greekIndexY(ch)
+ }
+ SHEET_EXTB_ROMANIAN_WIDE -> {
+ sheetX = romanianIndexX(ch)
+ sheetY = romanianIndexY(ch)
+ }
+ SHEET_EXTB_ROMANIAN_NARROW -> {
+ sheetX = 0
+ sheetY = 0
+ }
+ else -> {
+ sheetX = ch.toInt() % 16
+ sheetY = ch.toInt() / 16
+ }
+ }
+
+ val glyphW = getWidth("" + ch)
+ try {
+ sheetKey[prevInstance]!!.getSubImage(sheetX, sheetY).drawWithShadow(
+ Math.round(x + getWidthSubstr(s, i + 1) - glyphW).toFloat(),
+
+ // to deal with the height difference of the sheets
+ Math.round(y).toFloat() + (if (prevInstance == SHEET_CJK_PUNCT) -1 // height hack
+ else if (prevInstance == SHEET_FW_UNI) (H - H_HANGUL) / 2 // completely legit height adjustment
+ else 0).toFloat(),
+
+ scale.toFloat(), thisCol
+ )
+ }
+ catch (e: ArrayIndexOutOfBoundsException) {
+ // character that does not exist in the sheet. No render, pass.
+ }
+ catch (e1: RuntimeException) {
+ // System.err.println("[GameFontBase] RuntimeException raised while processing character '$ch' (U+${Integer.toHexString(ch.toInt()).toUpperCase()})")
+ // e1.printStackTrack()
+ }
+ }
+
+ }
+ if (prevInstance != -1) {
+ //sheetKey[prevInstance].endUse()
+ }
+
+ GL11.glEnd()
+ }
+
+ private fun getSheetType(c: Char): Int {
+ // EFs
+ if (isRomanianNarrow(c))
+ return SHEET_EXTB_ROMANIAN_NARROW
+ else if (isHangul(c))
+ return SHEET_HANGUL
+ else if (isKana(c))
+ return SHEET_KANA
+ else if (isUniHan(c))
+ return SHEET_UNIHAN
+ else if (isAscii(c))
+ return SHEET_ASCII_VARW
+ else if (isExtA(c))
+ return SHEET_EXTA_VARW
+ else if (isCyrilic(c))
+ return SHEET_CYRILIC_VARW
+ else if (isUniPunct(c))
+ return SHEET_UNI_PUNCT
+ else if (isCJKPunct(c))
+ return SHEET_CJK_PUNCT
+ else if (isFullwidthUni(c))
+ return SHEET_FW_UNI
+ else if (isGreek(c))
+ return SHEET_GREEK_VARW
+ else if (isRomanian(c))
+ return SHEET_EXTB_ROMANIAN_WIDE
+ else
+ return SHEET_UNKNOWN// fixed width punctuations
+ // fixed width
+ // fallback
+ }
+
+ /**
+ * Draw part of a string to the screen. Note that this will still position the text as though
+ * it's part of the bigger string.
+ * @param x
+ * *
+ * @param y
+ * *
+ * @param s
+ * *
+ * @param color
+ * *
+ * @param startIndex
+ * *
+ * @param endIndex
+ */
+ override fun drawString(x: Float, y: Float, s: String, color: Color, startIndex: Int, endIndex: Int) {
+ val unprintedHead = s.substring(0, startIndex)
+ val printedBody = s.substring(startIndex, endIndex)
+ val xoff = getWidth(unprintedHead)
+ drawString(x + xoff, y, printedBody, color)
+ }
+
+ fun buildWidthTable(sheet: SpriteSheet, codeOffset: Int, codeRange: IntRange, rows: Int = 16) {
+ fun Byte.toUint() = java.lang.Byte.toUnsignedInt(this)
+
+ /** @return Intarray(R, G, B, A) */
+ fun Texture.getPixel(x: Int, y: Int): IntArray {
+ val textureWidth = this.textureWidth
+ val hasAlpha = this.hasAlpha()
+
+ val offset = (if (hasAlpha) 4 else 3) * (textureWidth * y + x) // 4: # of channels (RGBA)
+
+ if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) {
+ return intArrayOf(
+ this.textureData[offset].toUint(),
+ this.textureData[offset + 1].toUint(),
+ this.textureData[offset + 2].toUint(),
+ if (hasAlpha)
+ this.textureData[offset + 3].toUint()
+ else 255
+ )
+ }
+ else {
+ return intArrayOf(
+ this.textureData[offset + 2].toUint(),
+ this.textureData[offset + 1].toUint(),
+ this.textureData[offset].toUint(),
+ if (hasAlpha)
+ this.textureData[offset + 3].toUint()
+ else 255
+ )
+ }
+ }
+
+ val binaryCodeOffset = 15
+
+ val cellW = sheet.getSubImage(0, 0).width + 1 // should be 16
+ val cellH = sheet.getSubImage(0, 0).height + 1 // should be 20
+
+ // control chars
+ for (ccode in codeRange) {
+ val glyphX = ccode % rows
+ val glyphY = ccode / rows
+
+ val codeStartX = (glyphX * cellW) + binaryCodeOffset
+ val codeStartY = (glyphY * cellH)
+
+ var glyphWidth = 0
+ for (downCtr in 0..3) {
+ // if alpha is not zero, assume it's 1
+ if (sheet.texture.getPixel(codeStartX, codeStartY + downCtr)[3] == 255) {
+ glyphWidth = glyphWidth or (1 shl downCtr)
+ }
+ }
+
+ asciiWidths[codeOffset + ccode] = glyphWidth
+ }
+ }
+
+ companion object {
+
+ lateinit internal var hangulSheet: SpriteSheet
+ lateinit internal var asciiSheet: SpriteSheet
+
+ internal val asciiWidths: HashMap = HashMap()
+
+ lateinit internal var extASheet: SpriteSheet
+ lateinit internal var kanaSheet: SpriteSheet
+ lateinit internal var cjkPunct: SpriteSheet
+ // static SpriteSheet uniHan;
+ lateinit internal var cyrilic: SpriteSheet
+ lateinit internal var fullwidthForms: SpriteSheet
+ lateinit internal var uniPunct: SpriteSheet
+ lateinit internal var wenQuanYi_1: SpriteSheet
+ lateinit internal var wenQuanYi_2: SpriteSheet
+ lateinit internal var greekSheet: SpriteSheet
+ lateinit internal var romanianSheet: SpriteSheet
+ lateinit internal var romanianSheetNarrow: SpriteSheet
+
+ internal val JUNG_COUNT = 21
+ internal val JONG_COUNT = 28
+
+ internal val W_ASIAN_PUNCT = 10
+ internal val W_HANGUL = 11
+ internal val W_KANA = 12
+ internal val W_UNIHAN = 16
+ internal val W_LATIN_WIDE = 9 // width of regular letters, including m
+ internal val W_LATIN_NARROW = 5 // width of letter f, t, i, l
+
+ internal val H = 20
+ internal val H_HANGUL = 16
+ internal val H_UNIHAN = 16
+ internal val H_KANA = 20
+
+ internal val SHEET_ASCII_VARW = 0
+ internal val SHEET_HANGUL = 1
+ internal val SHEET_EXTA_VARW = 2
+ internal val SHEET_KANA = 3
+ internal val SHEET_CJK_PUNCT = 4
+ internal val SHEET_UNIHAN = 5
+ internal val SHEET_CYRILIC_VARW = 6
+ internal val SHEET_FW_UNI = 7
+ internal val SHEET_UNI_PUNCT = 8
+ internal val SHEET_WENQUANYI_1 = 9
+ internal val SHEET_WENQUANYI_2 = 10
+ internal val SHEET_GREEK_VARW = 11
+ internal val SHEET_EXTB_ROMANIAN_WIDE = 12
+ internal val SHEET_EXTB_ROMANIAN_NARROW = 13
+
+ internal val SHEET_UNKNOWN = 254
+ internal val SHEET_COLOURCODE = 255
+
+ lateinit internal var sheetKey: Array
+
+ internal var interchar = 0
+ internal var scale = 1
+ set(value) {
+ if (value > 0) field = value
+ else throw IllegalArgumentException("Font scale cannot be zero or negative (input: $value)")
+ }
+
+ }// end of companion object
+}
+
+fun Image.drawWithShadow(x: Float, y: Float, color: Color) =
+ this.drawWithShadow(x, y, 1f, color)
+
+fun Image.drawWithShadow(x: Float, y: Float, scale: Float, color: Color) {
+ this.draw(x + 1, y + 1, scale, color.darker(0.5f))
+ this.draw(x , y + 1, scale, color.darker(0.5f))
+ this.draw(x + 1, y , scale, color.darker(0.5f))
+
+ this.draw(x, y, scale, color)
+}
diff --git a/demo/src/net/torvald/terrarum/imagefont/GameFontDemo.kt b/demo/src/net/torvald/terrarum/imagefont/GameFontDemo.kt
new file mode 100644
index 0000000..016a03c
--- /dev/null
+++ b/demo/src/net/torvald/terrarum/imagefont/GameFontDemo.kt
@@ -0,0 +1,94 @@
+package net.torvald.terrarum.imagefont
+
+import net.torvald.imagefont.GameFontImpl
+import org.newdawn.slick.*
+import java.io.File
+import java.io.FileInputStream
+import java.nio.file.FileSystems
+import java.nio.file.Files
+import java.util.*
+
+/**
+ * Created by SKYHi14 on 2017-02-16.
+ */
+
+class GameFontDemo : BasicGame("Terrarum Sans Bitmap Demo") {
+
+ lateinit var gameFont: Font
+
+ override fun init(p0: GameContainer?) {
+ gameFont = GameFontImpl()
+ }
+
+ override fun update(gc: GameContainer, delta: Int) {
+ }
+
+ override fun render(gc: GameContainer, g: Graphics) {
+ g.font = gameFont
+ g.background = Color(0x282828)
+ text.forEachIndexed { i, s ->
+ g.drawString(s, 10f, 10f + i * gameFont.lineHeight)
+ }
+ }
+
+ companion object {
+ lateinit var gameLocale: String
+ var hasError: Boolean
+ val text = ArrayList()
+
+ init {
+ try {
+ val prop = Properties()
+ prop.load(FileInputStream("./config.properties"))
+
+ gameLocale = prop.getProperty("locale")
+
+ if (gameLocale.length < 2)
+ throw IllegalArgumentException("Bad locale setting: “$gameLocale”")
+
+
+ Files.lines(FileSystems.getDefault().getPath("./text.txt")).forEach(
+ { text.add(it) }
+ )
+
+ hasError = false
+ }
+ catch (e: Exception) {
+ gameLocale = "enUS"
+ hasError = true
+ text.add("There was some problem loading the demo :(")
+ text.add("This is what JVM says:")
+ text.add("")
+ e.message!!.split('\n').forEach {
+ text.add(it)
+ }
+ }
+ }
+ }
+}
+
+fun main(args: Array) {
+
+ System.setProperty("java.library.path", "lib")
+ System.setProperty("org.lwjgl.librarypath", File("lib").absolutePath)
+
+ val WIDTH = 1000
+ val HEIGHT = 800
+
+ try {
+ val appgc = AppGameContainer(GameFontDemo())
+ appgc.setDisplayMode(WIDTH, HEIGHT, false)
+
+ appgc.setMultiSample(0)
+ appgc.setShowFPS(false)
+
+ // game will run normally even if it is not focused
+ appgc.setUpdateOnlyWhenVisible(false)
+ appgc.alwaysRender = true
+
+ appgc.start()
+ }
+ catch (ex: Exception) {
+ ex.printStackTrace()
+ }
+}
\ No newline at end of file
diff --git a/demo/src/net/torvald/terrarum/imagefont/GameFontImpl.kt b/demo/src/net/torvald/terrarum/imagefont/GameFontImpl.kt
new file mode 100644
index 0000000..a717267
--- /dev/null
+++ b/demo/src/net/torvald/terrarum/imagefont/GameFontImpl.kt
@@ -0,0 +1,75 @@
+package net.torvald.imagefont
+
+import net.torvald.terrarum.imagefont.GameFontDemo
+import org.newdawn.slick.*
+
+/**
+ * Created by minjaesong on 16-01-20.
+ */
+class GameFontImpl : GameFontBase() {
+
+ init {
+
+ GameFontBase.hangulSheet = SpriteSheet(
+ "./assets/graphics/fonts/hangul_johab.tga", GameFontBase.W_HANGUL, GameFontBase.H_HANGUL)
+ GameFontBase.asciiSheet = SpriteSheet(
+ "./assets/graphics/fonts/ascii_variable.tga", 15, 19, 1)
+ GameFontBase.extASheet = SpriteSheet(
+ "./assets/graphics/fonts/LatinExtA_variable.tga", 15, 19, 1)
+ GameFontBase.kanaSheet = SpriteSheet(
+ "./assets/graphics/fonts/kana.tga", GameFontBase.W_KANA, GameFontBase.H_KANA)
+ GameFontBase.cjkPunct = SpriteSheet(
+ "./assets/graphics/fonts/cjkpunct.tga", GameFontBase.W_ASIAN_PUNCT, GameFontBase.H_KANA)
+ /*uniHan = new SpriteSheet(
+ "./assets/graphics/fonts/unifont_unihan"
+ + ((!terrarum.gameLocale.contains("zh"))
+ ? "_ja" : "")
+ +".tga"
+ , W_UNIHAN, H_UNIHAN
+ );*/
+ GameFontBase.cyrilic = SpriteSheet(
+ when (GameFontDemo.gameLocale.substring(0..1)) {
+ "bg" -> "./assets/graphics/fonts/cyrilic_bulgarian_variable.tga"
+ "sr" -> "./assets/graphics/fonts/cyrilic_serbian_variable.tga"
+ else -> "./assets/graphics/fonts/cyrilic_variable.tga"
+ }, 15, 19, 1)
+ GameFontBase.fullwidthForms = SpriteSheet(
+ "./assets/graphics/fonts/fullwidth_forms.tga", GameFontBase.W_UNIHAN, GameFontBase.H_UNIHAN)
+ GameFontBase.uniPunct = SpriteSheet(
+ "./assets/graphics/fonts/unipunct.tga", GameFontBase.W_LATIN_WIDE, GameFontBase.H)
+ GameFontBase.wenQuanYi_1 = SpriteSheet(
+ "./assets/graphics/fonts/wenquanyi_11pt_part1.tga", 16, 18, 2)
+ GameFontBase.wenQuanYi_2 = SpriteSheet(
+ "./assets/graphics/fonts/wenquanyi_11pt_part2.tga", 16, 18, 2)
+ GameFontBase.greekSheet = SpriteSheet(
+ "./assets/graphics/fonts/greek_variable.tga", 15, 19, 1)
+ GameFontBase.romanianSheet = SpriteSheet(
+ "./assets/graphics/fonts/romana_wide.tga", GameFontBase.W_LATIN_WIDE, GameFontBase.H)
+ GameFontBase.romanianSheetNarrow = SpriteSheet(
+ "./assets/graphics/fonts/romana_narrow.tga", GameFontBase.W_LATIN_NARROW, GameFontBase.H)
+
+ val shk = arrayOf(
+ GameFontBase.asciiSheet,
+ GameFontBase.hangulSheet,
+ GameFontBase.extASheet,
+ GameFontBase.kanaSheet,
+ GameFontBase.cjkPunct,
+ null, // Full unihan, filler because we're using WenQuanYi
+ GameFontBase.cyrilic,
+ GameFontBase.fullwidthForms,
+ GameFontBase.uniPunct,
+ GameFontBase.wenQuanYi_1,
+ GameFontBase.wenQuanYi_2,
+ GameFontBase.greekSheet,
+ GameFontBase.romanianSheet,
+ GameFontBase.romanianSheetNarrow
+ )
+ GameFontBase.sheetKey = shk
+
+
+ buildWidthTable(asciiSheet, 0, 0..0xFF)
+ buildWidthTable(extASheet, 0x100, 0..0x7F)
+ buildWidthTable(cyrilic, 0x400, 0..0x5F)
+ buildWidthTable(greekSheet, 0x370, 0..0x5F)
+ }
+}
diff --git a/demo/text.txt b/demo/text.txt
new file mode 100644
index 0000000..ec1567b
--- /dev/null
+++ b/demo/text.txt
@@ -0,0 +1,37 @@
+Set locale in config.properties to “bgBG” for alternative Bulgarian letters, “srSR” for Serbian.
+
+ABCDEFGHIJKLM NOPQRSTUVWXYZ
+abcdefghijklm nopqrstuvwxyz
+
+Syö salmiakkia, koska se on hyvää sinulle
+
+The Olympic marmot (Marmota olympus) is a rodent in the squirrel family, Sciuridae.
+It lives only in the U.S. state of Washington, at middle elevations on the Olympic Peninsula.
+About the size of a domestic cat, an adult weighs around 8 kg (18 lb) in summer.
+
+Brná je část statutárního a krajského města Ústí nad Labem v České republice, spadající
+pod městský obvod Ústí nad Labem-Střekov. Nachází se asi pět kilometrů jižně od centra
+města v Českém středohoří na pravém břehu řeky Labe.
+
+Malaysia er en forholdsvis ung stat. Sin endelige udstrækning fik den først i 1965 efter
+at Singapore trak sig ud. Staten blev grundlagt ved en sammenslutning af flere tidligere
+britiske besiddelser, foreløbigt i 1957 og endeligt i 1963.
+
+Το θάλλιο συνοδεύει κυρίως θειούχα ορυκτά βασικών μετάλλων, όπως ο σφαλερίτης, ο σιδηροπυρίτης
+και ο γαληνίτης ενώ αναφέρονται και εμφανίσεις του σε κονδύλους μαγγανίου στους βυθούς των ωκεανών.
+διαφυλάξτε γενικά τη ζωή σας από βαθειά ψυχικά τραύματα
+ΔΙΑΦΥΛΆΞΤΕ ΓΕΝΙΚΆ ΤΗ ΖΩΉ ΣΑΣ ΑΠΌ ΒΑΘΕΙΆ ΨΥΧΙΚΆ ΤΡΑΎΜΑΤΑ
+Широкая электрификация южных губерний даст мощный толчок подъёму сельского хозяйства.
+Příliš žluťoučký kůň úpěl ďábelské ódy. Árvíztűrő tükörfúrógép.
+Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich.
+Pijamalı hasta yağız şoföre çabucak güvendi. Kŕdeľ ďatľov učí koňa žrať kôru.
+Voix ambiguë d'un cœur qui au zéphyr préfère les jattes de kiwi.
+Înjurând pițigăiat, zoofobul comandă vexat whisky și tequila.
+
+sjaldgæft ekki stjórnarskrárvarin
+
+Also supports:
+‛Unicode’ „quotation marks“—dashes…‼
+으웽~. 얘! 위에 이 애 우유의 양 외워와! 아오~ 왜요? 어여! 예... 웬 초콜릿? 제가 원했던 건 뻥튀기 쬐끔과 의류예요. 얘야, 왜 또 불평? 퀡퇣풿횂
+とりなくこゑす ゆめさませ みよあけわたる ひんかしを そらいろはえて おきつへに ほふねむれゐぬ もやのうち
+鳥啼く声す 夢覚ませ 見よ明け渡る 東を 空色栄えて 沖つ辺に 帆船群れゐぬ 靄の中,
\ No newline at end of file