From b2fe8c3304c1bc58f00c6bcfcd1b878047bc8c4c Mon Sep 17 00:00:00 2001 From: minjaesong Date: Tue, 20 Jun 2017 18:36:58 +0900 Subject: [PATCH] release candidate --- .gitignore | 3 + .idea/.name | 1 + .idea/artifacts/TerrarumSansBitmap.xml | 11 + .idea/libraries/KotlinJavaRuntime.xml | 12 + .idea/libraries/lib.xml | 10 + .idea/misc.xml | 74 ++ .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .idea/workspace.xml | 511 ++++++++++++ BuildJAR_TerrarumSansBitmap.iml | 13 + META-INF/MANIFEST.MF | 2 + README.md | 47 +- TerrarumSansBitmap.jar | Bin 0 -> 37283 bytes assets/hangul_johab.tga | Bin 268844 -> 268844 bytes demo/.idea/kotlinc.xml | 6 + demo/.idea/workspace.xml | 100 +-- .../terrarumsansbitmap}/gdx/GameFontBase.kt | 24 + .../gdx/TextureRegionPack.kt | 24 + .../net/torvald/terrarumsansbitmap}/readme.md | 0 .../slick2d/GameFontBase.kt | 724 ++++++++++++++++++ terrarumsansbitmap/slick2d/GameFontBase.kt | 551 ------------- terrarumsansbitmap/slick2d/GameFontImpl.kt | 81 -- 22 files changed, 1506 insertions(+), 702 deletions(-) create mode 100644 .idea/.name create mode 100644 .idea/artifacts/TerrarumSansBitmap.xml create mode 100644 .idea/libraries/KotlinJavaRuntime.xml create mode 100644 .idea/libraries/lib.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 BuildJAR_TerrarumSansBitmap.iml create mode 100644 META-INF/MANIFEST.MF create mode 100644 TerrarumSansBitmap.jar create mode 100644 demo/.idea/kotlinc.xml rename {terrarumsansbitmap => src/net/torvald/terrarumsansbitmap}/gdx/GameFontBase.kt (95%) rename {terrarumsansbitmap => src/net/torvald/terrarumsansbitmap}/gdx/TextureRegionPack.kt (62%) rename {terrarumsansbitmap => src/net/torvald/terrarumsansbitmap}/readme.md (100%) create mode 100644 src/net/torvald/terrarumsansbitmap/slick2d/GameFontBase.kt delete mode 100644 terrarumsansbitmap/slick2d/GameFontBase.kt delete mode 100644 terrarumsansbitmap/slick2d/GameFontImpl.kt diff --git a/.gitignore b/.gitignore index 51eb804..0698352 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ demo/out/* demo/lib/* +demo/assets/* +out/* +lib/* \ No newline at end of file diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..ec36fc9 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +BuildJAR_TerrarumSansBitmap \ No newline at end of file diff --git a/.idea/artifacts/TerrarumSansBitmap.xml b/.idea/artifacts/TerrarumSansBitmap.xml new file mode 100644 index 0000000..551cf89 --- /dev/null +++ b/.idea/artifacts/TerrarumSansBitmap.xml @@ -0,0 +1,11 @@ + + + $PROJECT_DIR$/out/artifacts/TerrarumSansBitmap + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/KotlinJavaRuntime.xml b/.idea/libraries/KotlinJavaRuntime.xml new file mode 100644 index 0000000..c630c0b --- /dev/null +++ b/.idea/libraries/KotlinJavaRuntime.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/lib.xml b/.idea/libraries/lib.xml new file mode 100644 index 0000000..fa8838a --- /dev/null +++ b/.idea/libraries/lib.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..cac86c6 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..e68d521 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..65ee9a9 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,511 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1497950823354 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TerrarumSansBitmap + + + + + + + + No facets are configured + + + + + + + + + + + + + + + 1.8 + + + + + + + + BuildJAR_TerrarumSansBitmap + + + + + + + + lib + + + + + + + + \ No newline at end of file diff --git a/BuildJAR_TerrarumSansBitmap.iml b/BuildJAR_TerrarumSansBitmap.iml new file mode 100644 index 0000000..c33f24f --- /dev/null +++ b/BuildJAR_TerrarumSansBitmap.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF new file mode 100644 index 0000000..59499bc --- /dev/null +++ b/META-INF/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/README.md b/README.md index 9e5576e..e3fd143 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,49 @@ You can contribute to the font by fixing wrong glyphs, suggesting better ones, e Font Spritesheets are stored in ```assets/graphics/fonts``` directory. Image format must be TGA with Alpha — no PNG. If someone needs PNG, they can batch-convert the font using utils like ImageMagick. +## Using on LibGDX + +On your code (Kotlin): + + class YourGame : Game() { + + lateinit var fontGame: Font + + override fun create() { + fontGame = GameFontBase(path_to_assets) + ... + } + + override fun render() { + batch.begin() + ... + fontGame.draw(batch, text, ...) + ... + batch.end() + } + } + +On your code (Java): + + class YourGame extends BasicGame { + + Font fontGame; + + @Override void create() { + fontGame = new GameFontBase(path_to_assets); + ... + } + + @Override void render() { + batch.begin(); + ... + fontGame.draw(batch, text, ...); + ... + batch.end(); + } + } + + ## Using on Slick2d On your code (Kotlin): @@ -31,7 +74,7 @@ On your code (Kotlin): lateinit var fontGame: Font override fun init(gc: GameContainer) { - fontGame = GameFontImpl() + fontGame = GameFontBase(path_to_assets) ... } @@ -48,7 +91,7 @@ On your code (Java): Font fontGame; @Override void init(GameContainer gc) { - fontGame = new GameFontImpl(); + fontGame = new GameFontBase(path_to_assets); ... } diff --git a/TerrarumSansBitmap.jar b/TerrarumSansBitmap.jar new file mode 100644 index 0000000000000000000000000000000000000000..608dbc56516fb8ccebf6a253a27c37836c4c84c8 GIT binary patch literal 37283 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 delta 230 zcmZ3pMPSVqfeo1=lh2k(O!ob!GX3pwCX?y93d}AObps~9<%*bo_861R^t()qD${#k zGU`mOl~kF2*MQk%`q>~Rk;!YtHKxlNGHYz+6~DtYeQ!9U0Z>oCL|vW9YfEJ&>y`yf q?rl_=?(4#&v)Q+0M#Sc~Ts53Dx2oB + + + + \ No newline at end of file diff --git a/demo/.idea/workspace.xml b/demo/.idea/workspace.xml index 9aa6736..5e92d94 100644 --- a/demo/.idea/workspace.xml +++ b/demo/.idea/workspace.xml @@ -25,7 +25,7 @@ - + @@ -36,7 +36,7 @@ - + @@ -389,7 +389,6 @@ - @@ -410,6 +409,7 @@ + @@ -431,6 +431,26 @@ - - - - - - - - + @@ -634,18 +615,10 @@ - - - - - - - - - + @@ -668,19 +641,11 @@ - - - - - - - - + - @@ -688,7 +653,6 @@ - @@ -710,7 +674,7 @@ - + @@ -721,7 +685,7 @@ - + diff --git a/terrarumsansbitmap/gdx/GameFontBase.kt b/src/net/torvald/terrarumsansbitmap/gdx/GameFontBase.kt similarity index 95% rename from terrarumsansbitmap/gdx/GameFontBase.kt rename to src/net/torvald/terrarumsansbitmap/gdx/GameFontBase.kt index 970d46c..1828324 100644 --- a/terrarumsansbitmap/gdx/GameFontBase.kt +++ b/src/net/torvald/terrarumsansbitmap/gdx/GameFontBase.kt @@ -1,3 +1,27 @@ +/* + * Terrarum Sans Bitmap + * + * Copyright (c) 2017 Minjae Song (Torvald) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package net.torvald.terrarumsansbitmap.gdx import com.badlogic.gdx.Gdx diff --git a/terrarumsansbitmap/gdx/TextureRegionPack.kt b/src/net/torvald/terrarumsansbitmap/gdx/TextureRegionPack.kt similarity index 62% rename from terrarumsansbitmap/gdx/TextureRegionPack.kt rename to src/net/torvald/terrarumsansbitmap/gdx/TextureRegionPack.kt index 9e9aed6..f7675a9 100644 --- a/terrarumsansbitmap/gdx/TextureRegionPack.kt +++ b/src/net/torvald/terrarumsansbitmap/gdx/TextureRegionPack.kt @@ -1,3 +1,27 @@ +/* + * Terrarum Sans Bitmap + * + * Copyright (c) 2017 Minjae Song (Torvald) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package net.torvald.terrarumsansbitmap.gdx import com.badlogic.gdx.files.FileHandle diff --git a/terrarumsansbitmap/readme.md b/src/net/torvald/terrarumsansbitmap/readme.md similarity index 100% rename from terrarumsansbitmap/readme.md rename to src/net/torvald/terrarumsansbitmap/readme.md diff --git a/src/net/torvald/terrarumsansbitmap/slick2d/GameFontBase.kt b/src/net/torvald/terrarumsansbitmap/slick2d/GameFontBase.kt new file mode 100644 index 0000000..af0b683 --- /dev/null +++ b/src/net/torvald/terrarumsansbitmap/slick2d/GameFontBase.kt @@ -0,0 +1,724 @@ +/* + * Terrarum Sans Bitmap + * + * Copyright (c) 2017 Minjae Song (Torvald) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.torvald.terrarumsansbitmap.slick2d + +import org.newdawn.slick.Color +import org.newdawn.slick.Font +import org.newdawn.slick.Image +import org.newdawn.slick.SpriteSheet +import org.newdawn.slick.opengl.Texture +import java.io.BufferedOutputStream +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.util.* +import java.util.zip.GZIPInputStream + +/** + * LibGDX port of Terrarum Sans Bitmap implementation + * + * Filename and Extension for the spritesheet is hard-coded, which are: + * + * - ascii_variable.tga + * - hangul_johab.tga + * - LatinExtA_variable.tga + * - LatinExtB_variable.tga + * - kana.tga + * - cjkpunct.tga + * - wenquanyi.tga.gz + * - cyrillic_variable.tga + * - fullwidth_forms.tga + * - unipunct_variable.tga + * - greek_variable.tga + * - thai_variable.tga + * - puae000-e0ff.tga + * + * + * Glyphs are drawn lazily (calculated on-the-fly, rather than load up all), which is inevitable as we just can't load + * up 40k+ characters on the machine, which will certainly make loading time painfully long. + * + * @param noShadow Self-explanatory + * @param flipY If you have Y-down coord system implemented on your GDX (e.g. legacy codebase), set this to ```true``` so that the shadow won't be upside-down. For glyph getting upside-down, set ```TextureRegionPack.globalFlipY = true```. + * + * Created by minjaesong on 2017-06-15. + */ +class GameFontBase(fontDir: String, val noShadow: Boolean = false) : Font { + + 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() in codeRange[SHEET_HANGUL] + private fun isAscii(c: Char) = c.toInt() in codeRange[SHEET_ASCII_VARW] + //private fun isRunic(c: Char) = runicList.contains(c) + private fun isExtA(c: Char) = c.toInt() in codeRange[SHEET_EXTA_VARW] + private fun isExtB(c: Char) = c.toInt() in codeRange[SHEET_EXTB_VARW] + private fun isKana(c: Char) = c.toInt() in codeRange[SHEET_KANA] + private fun isCJKPunct(c: Char) = c.toInt() in codeRange[SHEET_CJK_PUNCT] + private fun isUniHan(c: Char) = c.toInt() in codeRange[SHEET_UNIHAN] + private fun isCyrilic(c: Char) = c.toInt() in codeRange[SHEET_CYRILIC_VARW] + private fun isFullwidthUni(c: Char) = c.toInt() in codeRange[SHEET_FW_UNI] + private fun isUniPunct(c: Char) = c.toInt() in codeRange[SHEET_UNI_PUNCT] + private fun isGreek(c: Char) = c.toInt() in codeRange[SHEET_GREEK_VARW] + private fun isThai(c: Char) = c.toInt() in codeRange[SHEET_THAI_VARW] + private fun isDiacritics(c: Char) = c.toInt() in 0xE34..0xE3A + || c.toInt() in 0xE47..0xE4E + || c.toInt() == 0xE31 + private fun isCustomSym(c: Char) = c.toInt() in codeRange[SHEET_CUSTOM_SYM] + private fun isArmenian(c: Char) = c.toInt() in codeRange[SHEET_HAYEREN_VARW] + private fun isKartvelian(c: Char) = c.toInt() in codeRange[SHEET_KARTULI_VARW] + private fun isIPA(c: Char) = c.toInt() in codeRange[SHEET_IPA_VARW] + + + + private fun extAindexX(c: Char) = (c.toInt() - 0x100) % 16 + private fun extAindexY(c: Char) = (c.toInt() - 0x100) / 16 + + private fun extBindexX(c: Char) = (c.toInt() - 0x180) % 16 + private fun extBindexY(c: Char) = (c.toInt() - 0x180) / 16 + + //private fun runicIndexX(c: Char) = runicList.indexOf(c) % 16 + //private fun runicIndexY(c: Char) = runicList.indexOf(c) / 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 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 unihanIndexX(c: Char) = (c.toInt() - 0x3400) % 256 + private fun unihanIndexY(c: Char) = (c.toInt() - 0x3400) / 256 + + private fun greekIndexX(c: Char) = (c.toInt() - 0x370) % 16 + private fun greekIndexY(c: Char) = (c.toInt() - 0x370) / 16 + + private fun thaiIndexX(c: Char) = (c.toInt() - 0xE00) % 16 + private fun thaiIndexY(c: Char) = (c.toInt() - 0xE00) / 16 + + private fun symbolIndexX(c: Char) = (c.toInt() - 0xE000) % 16 + private fun symbolIndexY(c: Char) = (c.toInt() - 0xE000) / 16 + + private fun armenianIndexX(c: Char) = (c.toInt() - 0x530) % 16 + private fun armenianIndexY(c: Char) = (c.toInt() - 0x530) / 16 + + private fun kartvelianIndexX(c: Char) = (c.toInt() - 0x10D0) % 16 + private fun kartvelianIndexY(c: Char) = (c.toInt() - 0x10D0) / 16 + + private fun ipaIndexX(c: Char) = (c.toInt() - 0x250) % 16 + private fun ipaIndexY(c: Char) = (c.toInt() - 0x250) / 16 + + private val unihanWidthSheets = arrayOf( + SHEET_UNIHAN, + SHEET_FW_UNI + ) + private val variableWidthSheets = arrayOf( + SHEET_ASCII_VARW, + SHEET_EXTA_VARW, + SHEET_EXTB_VARW, + SHEET_CYRILIC_VARW, + SHEET_UNI_PUNCT, + SHEET_GREEK_VARW, + SHEET_THAI_VARW, + SHEET_HAYEREN_VARW, + SHEET_KARTULI_VARW, + SHEET_IPA_VARW + ) + + private val fontParentDir = if (fontDir.endsWith('/') || fontDir.endsWith('\\')) fontDir else "$fontDir/" + private val fileList = arrayOf( // MUST BE MATCHING WITH SHEET INDICES!! + "ascii_variable.tga", + "hangul_johab.tga", + "LatinExtA_variable.tga", + "LatinExtB_variable.tga", + "kana.tga", + "cjkpunct.tga", + "wenquanyi.tga.gz", + "cyrilic_variable.tga", + "fullwidth_forms.tga", + "unipunct_variable.tga", + "greek_variable.tga", + "thai_variable.tga", + "hayeren_variable.tga", + "kartuli_variable.tga", + "ipa_ext_variable.tga", + "puae000-e0ff.tga" + ) + private val cyrilic_bg = "cyrilic_bulgarian_variable.tga" + private val cyrilic_sr = "cyrilic_serbian_variable.tga" + private val codeRange = arrayOf( // MUST BE MATCHING WITH SHEET INDICES!! + 0..0xFF, + 0xAC00..0xD7A3, + 0x100..0x17F, + 0x180..0x24F, + 0x3040..0x30FF, + 0x3000..0x303F, + 0x3400..0x9FFF, + 0x400..0x52F, + 0xFF00..0xFF1F, + 0x2000..0x205F, + 0x370..0x3CE, + 0xE00..0xE5F, + 0x530..0x58F, + 0x10D0..0x10FF, + 0x250..0x2AF, + 0xE000..0xE0FF + ) + private val glyphWidths: HashMap = HashMap() // if the value is negative, it's diacritics + private val sheets: Array + + + init { + val sheetsPack = ArrayList() + + // first we create pixmap to read pixels, then make texture using pixmap + fileList.forEachIndexed { index, it -> + val isVariable1 = it.endsWith("_variable.tga") + val isVariable2 = variableWidthSheets.contains(index) + val isVariable = isVariable1 && isVariable2 + + // idiocity check + if (isVariable1 && !isVariable2) + throw Error("[TerrarumSansBitmap] font is named as variable on the name but not enlisted as") + else if (!isVariable1 && isVariable2) + throw Error("[TerrarumSansBitmap] font is enlisted as variable on the name but not named as") + + + val image: Image + + + // unpack gz if applicable + if (it.endsWith(".gz")) { + val gzi = GZIPInputStream(FileInputStream(fontParentDir + it)) + val wholeFile = gzi.readBytes() + gzi.close() + val fos = BufferedOutputStream(FileOutputStream("tmp_wenquanyi.tga")) + fos.write(wholeFile) + fos.flush() + fos.close() + + image = Image("tmp_wenquanyi.tga") + + File("tmp_wenquanyi.tga").delete() + } + else { + image = Image(fontParentDir + it) + } + + val texture = image.texture + + if (isVariable) { + println("[TerrarumSansBitmap] loading texture $it [VARIABLE]") + buildWidthTable(texture, codeRange[index], 16) + } + else { + println("[TerrarumSansBitmap] loading texture $it") + } + + val texRegPack = if (isVariable) { + SpriteSheet(image, W_VAR_INIT, H - 1, HGAP_VAR) + } + else if (index == SHEET_UNIHAN) { + SpriteSheet(image, W_UNIHAN, H_UNIHAN) // the only exception that is height is 16 + } + // below they all have height of 20 'H' + else if (index == SHEET_FW_UNI) { + SpriteSheet(image, W_UNIHAN, H) + } + else if (index == SHEET_CJK_PUNCT) { + SpriteSheet(image, W_ASIAN_PUNCT, H) + } + else if (index == SHEET_KANA) { + SpriteSheet(image, W_KANA, H) + } + else if (index == SHEET_HANGUL) { + SpriteSheet(image, W_HANGUL, H) + } + else if (index == SHEET_CUSTOM_SYM) { + SpriteSheet(image, SIZE_CUSTOM_SYM, SIZE_CUSTOM_SYM) // TODO variable + } + else throw IllegalArgumentException("[TerrarumSansBitmap] Unknown sheet index: $index") + + + sheetsPack.add(texRegPack) + } + + sheets = sheetsPack.toTypedArray() + } + + private var localeBuffer = "" + + fun reload(locale: String) { + if (!localeBuffer.startsWith("ru") && locale.startsWith("ru")) { + val image = Image(fontParentDir + fileList[SHEET_CYRILIC_VARW]) + sheets[SHEET_CYRILIC_VARW].destroy() + sheets[SHEET_CYRILIC_VARW] = SpriteSheet(image, W_VAR_INIT, H, HGAP_VAR, 0) + } + else if (!localeBuffer.startsWith("bg") && locale.startsWith("bg")) { + val image = Image(fontParentDir + cyrilic_bg) + sheets[SHEET_CYRILIC_VARW].destroy() + sheets[SHEET_CYRILIC_VARW] = SpriteSheet(image, W_VAR_INIT, H, HGAP_VAR, 0) + } + else if (!localeBuffer.startsWith("sr") && locale.startsWith("sr")) { + val image = Image(fontParentDir + cyrilic_sr) + sheets[SHEET_CYRILIC_VARW].destroy() + sheets[SHEET_CYRILIC_VARW] = SpriteSheet(image, W_VAR_INIT, H, HGAP_VAR, 0) + } + + localeBuffer = locale + } + + override fun getLineHeight(): Int = H + override fun getHeight(p0: String) = lineHeight + + + + + private val offsetUnihan = (H - H_UNIHAN) / 2 + private val offsetCustomSym = (H - SIZE_CUSTOM_SYM) / 2 + + private var textBuffer: CharSequence = "" + private var textBWidth = intArrayOf() // absolute posX of glyphs from print-origin + private var textBGSize = intArrayOf() // width of each glyph + + override fun drawString(x: Float, y: Float, str: String) { + drawString(x, y, str, Color.white) + } + + override fun drawString(p0: Float, p1: Float, p2: String?, p3: Color?, p4: Int, p5: Int) { + throw UnsupportedOperationException() + } + + override fun drawString(x: Float, y: Float, str: String, color: Color) { + if (textBuffer != str) { + textBuffer = str + val widths = getWidthOfCharSeq(str) + + textBGSize = widths + + textBWidth = IntArray(str.length, { charIndex -> + if (charIndex == 0) + 0 + else { + var acc = 0 + (0..charIndex - 1).forEach { acc += maxOf(0, widths[it]) } // don't accumulate diacrtics (which has negative value) + /*return*/acc + } + }) + } + + + //print("[TerrarumSansBitmap] widthTable for $textBuffer: ") + //textBWidth.forEach { print("$it ") }; println() + + + val mainCol = color + val shadowCol = color.darker(0.5f) + + + textBuffer.forEachIndexed { index, c -> + val sheetID = getSheetType(c) + val sheetXY = getSheetwisePosition(c) + + //println("[TerrarumSansBitmap] sprite: $sheetID:${sheetXY[0]}x${sheetXY[1]}") + + if (sheetID == SHEET_HANGUL) { + val hangulSheet = sheets[SHEET_HANGUL] + val hIndex = c.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) + + + if (!noShadow) { + hangulSheet.getSubImage(indexCho, choRow ).draw(x + textBWidth[index] + 1, y, shadowCol) + hangulSheet.getSubImage(indexCho, choRow ).draw(x + textBWidth[index] , y, shadowCol) + hangulSheet.getSubImage(indexCho, choRow ).draw(x + textBWidth[index] + 1, y, shadowCol) + + hangulSheet.getSubImage(indexJung, jungRow).draw(x + textBWidth[index] + 1, y, shadowCol) + hangulSheet.getSubImage(indexJung, jungRow).draw(x + textBWidth[index] , y, shadowCol) + hangulSheet.getSubImage(indexJung, jungRow).draw(x + textBWidth[index] + 1, y, shadowCol) + + hangulSheet.getSubImage(indexJong, jongRow).draw(x + textBWidth[index] + 1, y, shadowCol) + hangulSheet.getSubImage(indexJong, jongRow).draw(x + textBWidth[index] , y, shadowCol) + hangulSheet.getSubImage(indexJong, jongRow).draw(x + textBWidth[index] + 1, y, shadowCol) + } + + + hangulSheet.getSubImage(indexCho, choRow ).draw(x + textBWidth[index], y, mainCol) + hangulSheet.getSubImage(indexJung, jungRow).draw(x + textBWidth[index], y, mainCol) + hangulSheet.getSubImage(indexJong, jongRow).draw(x + textBWidth[index], y, mainCol) + } + else { + try { + val offset = if (!isDiacritics(c)) 0 else { + if (index > 0) // LIMITATION: does not support double (or more) diacritics properly + (textBGSize[index] - textBGSize[index - 1]) / 2 + else + textBGSize[index] + } + + if (!noShadow) { + sheets[sheetID].getSubImage(sheetXY[0], sheetXY[1]).draw( + x + textBWidth[index] + 1 + offset, + y + (if (sheetID == SHEET_UNIHAN) // evil exceptions + offsetUnihan + else if (sheetID == SHEET_CUSTOM_SYM) + offsetCustomSym + else + 0), + shadowCol + ) + sheets[sheetID].getSubImage(sheetXY[0], sheetXY[1]).draw( + x + textBWidth[index] + offset, + y + (if (sheetID == SHEET_UNIHAN) // evil exceptions + offsetUnihan + 1 + else if (sheetID == SHEET_CUSTOM_SYM) + offsetCustomSym + 1 + else + 1), + shadowCol + ) + sheets[sheetID].getSubImage(sheetXY[0], sheetXY[1]).draw( + x + textBWidth[index] + 1 + offset, + y + (if (sheetID == SHEET_UNIHAN) // evil exceptions + offsetUnihan + 1 + else if (sheetID == SHEET_CUSTOM_SYM) + offsetCustomSym + 1 + else + 1), + shadowCol + ) + } + + + sheets[sheetID].getSubImage(sheetXY[0], sheetXY[1]).draw( + x + textBWidth[index] + offset, + y + if (sheetID == SHEET_UNIHAN) // evil exceptions + offsetUnihan + else if (sheetID == SHEET_CUSTOM_SYM) + offsetCustomSym + else 0, + mainCol + ) + } + catch (noSuchGlyph: ArrayIndexOutOfBoundsException) { + } + } + } + + } + + + fun dispose() { + sheets.forEach { it.destroy() } + } + + private fun getWidthOfCharSeq(s: CharSequence): IntArray { + val len = IntArray(s.length) + for (i in 0..s.lastIndex) { + val chr = s[i] + val ctype = getSheetType(s[i]) + + if (variableWidthSheets.contains(ctype)) { + if (!glyphWidths.containsKey(chr.toInt())) { + println("[TerrarumSansBitmap] no width data for glyph number ${Integer.toHexString(chr.toInt()).toUpperCase()}") + len[i] = W_LATIN_WIDE + } + + len[i] = glyphWidths[chr.toInt()]!! + } + else if (ctype == SHEET_CJK_PUNCT) + len[i] = W_ASIAN_PUNCT + else if (ctype == SHEET_HANGUL) + len[i] = W_HANGUL + else if (ctype == SHEET_KANA) + len[i] = W_KANA + else if (unihanWidthSheets.contains(ctype)) + len[i] = W_UNIHAN + else if (ctype == SHEET_CUSTOM_SYM) + len[i] = SIZE_CUSTOM_SYM + else + len[i] = W_LATIN_WIDE + + if (scale > 1) len[i] *= scale + + if (i < s.lastIndex) len[i] += interchar + } + return len + } + + private fun getSheetType(c: Char): Int { + 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 (isExtB(c)) + return SHEET_EXTB_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 (isThai(c)) + return SHEET_THAI_VARW + else if (isCustomSym(c)) + return SHEET_CUSTOM_SYM + else if (isArmenian(c)) + return SHEET_HAYEREN_VARW + else if (isKartvelian(c)) + return SHEET_KARTULI_VARW + else if (isIPA(c)) + return SHEET_IPA_VARW + else + return SHEET_UNKNOWN + // fixed width + // fallback + } + + private fun getSheetwisePosition(ch: Char): IntArray { + val sheetX: Int; val sheetY: Int + when (getSheetType(ch)) { + SHEET_UNIHAN -> { + sheetX = unihanIndexX(ch) + sheetY = unihanIndexY(ch) + } + SHEET_EXTA_VARW -> { + sheetX = extAindexX(ch) + sheetY = extAindexY(ch) + } + SHEET_EXTB_VARW -> { + sheetX = extBindexX(ch) + sheetY = extBindexY(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_THAI_VARW -> { + sheetX = thaiIndexX(ch) + sheetY = thaiIndexY(ch) + } + SHEET_CUSTOM_SYM -> { + sheetX = symbolIndexX(ch) + sheetY = symbolIndexY(ch) + } + SHEET_HAYEREN_VARW -> { + sheetX = armenianIndexX(ch) + sheetY = armenianIndexY(ch) + } + SHEET_KARTULI_VARW -> { + sheetX = kartvelianIndexX(ch) + sheetY = kartvelianIndexY(ch) + } + SHEET_IPA_VARW -> { + sheetX = ipaIndexX(ch) + sheetY = ipaIndexY(ch) + } + else -> { + sheetX = ch.toInt() % 16 + sheetY = ch.toInt() / 16 + } + } + + return intArrayOf(sheetX, sheetY) + } + + fun buildWidthTable(texture: Texture, codeRange: IntRange, cols: Int = 16) { + val binaryCodeOffset = W_VAR_INIT + + val cellW = W_VAR_INIT + 1 + val cellH = H + + for (code in codeRange) { + + val cellX = ((code - codeRange.start) % cols) * cellW + val cellY = ((code - codeRange.start) / cols) * cellH + + val codeStartX = cellX + binaryCodeOffset + val codeStartY = cellY + + var glyphWidth = 0 + + for (downCtr in 0..3) { + // if ALPHA is not zero, assume it's 1 + if (texture.textureData[4 * (codeStartX + (codeStartY + downCtr) * texture.textureWidth) + 3] != 0.toByte()) { + glyphWidth = glyphWidth or (1 shl downCtr) + } + } + + val isDiacritics = texture.textureData[4 * (codeStartX + (codeStartY + H - 1) * texture.textureWidth) + 3] != 0.toByte() + if (isDiacritics) + glyphWidth = -glyphWidth + + glyphWidths[code] = glyphWidth + } + } + + + override fun getWidth(text: String): Int { + return getWidthOfCharSeq(text).sum() + } + + companion object { + + internal val JUNG_COUNT = 21 + internal val JONG_COUNT = 28 + + internal val W_ASIAN_PUNCT = 10 + internal val W_HANGUL = 12 + internal val W_KANA = 12 + internal val W_UNIHAN = 16 + internal val W_LATIN_WIDE = 9 // width of regular letters + internal val W_VAR_INIT = 15 + + internal val HGAP_VAR = 1 + + internal val H = 20 + internal val H_UNIHAN = 16 + + internal val SIZE_CUSTOM_SYM = 18 + + internal val SHEET_ASCII_VARW = 0 + internal val SHEET_HANGUL = 1 + internal val SHEET_EXTA_VARW = 2 + internal val SHEET_EXTB_VARW = 3 + internal val SHEET_KANA = 4 + internal val SHEET_CJK_PUNCT = 5 + internal val SHEET_UNIHAN = 6 + internal val SHEET_CYRILIC_VARW = 7 + internal val SHEET_FW_UNI = 8 + internal val SHEET_UNI_PUNCT = 9 + internal val SHEET_GREEK_VARW = 10 + internal val SHEET_THAI_VARW = 11 + internal val SHEET_HAYEREN_VARW = 12 + internal val SHEET_KARTULI_VARW = 13 + internal val SHEET_IPA_VARW = 14 + internal val SHEET_CUSTOM_SYM = 15 + + internal val SHEET_UNKNOWN = 254 + + /** + * Runic letters list used for game. The set is + * Younger Futhark + Medieval rune 'e' + Punct + Runic Almanac + + * BEWARE OF SIMILAR-LOOKING RUNES, especially: + + * * Algiz ᛉ instead of Maðr ᛘ + + * * Short-Twig Hagall ᚽ instead of Runic Letter E ᛂ + + * * Runic Letter OE ᚯ instead of Óss ᚬ + + * Examples: + * ᛭ᛋᛁᚴᚱᛁᚦᛦ᛭ + * ᛭ᛂᛚᛋᛅ᛭ᛏᚱᚢᛏᚾᛁᚾᚴᚢᚾᛅ᛬ᛅᚱᚾᛅᛏᛅᛚᛋ + */ + //internal val runicList = arrayOf('ᚠ', 'ᚢ', 'ᚦ', 'ᚬ', 'ᚱ', 'ᚴ', 'ᚼ', 'ᚾ', 'ᛁ', 'ᛅ', 'ᛋ', 'ᛏ', 'ᛒ', 'ᛘ', 'ᛚ', 'ᛦ', 'ᛂ', '᛬', '᛫', '᛭', 'ᛮ', 'ᛯ', 'ᛰ') + // TODO expand to full Unicode runes + + var interchar = 0 + var scale = 1 + set(value) { + if (value > 0) field = value + else throw IllegalArgumentException("Font scale cannot be zero or negative (input: $value)") + } + } + +} \ No newline at end of file diff --git a/terrarumsansbitmap/slick2d/GameFontBase.kt b/terrarumsansbitmap/slick2d/GameFontBase.kt deleted file mode 100644 index b1443dc..0000000 --- a/terrarumsansbitmap/slick2d/GameFontBase.kt +++ /dev/null @@ -1,551 +0,0 @@ -package net.torvald.terrarumsansbitmap.slick2d - -import net.torvald.terrarum.getPixel -import org.lwjgl.opengl.GL11 -import org.newdawn.slick.* -import java.util.* - -/** - * Created by minjaesong on 16-01-27. - */ -open class GameFontBase(val noShadow: Boolean) : Font { - - 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() in 0xAC00..0xD7A3 - private fun isAscii(c: Char) = c.toInt() in 0x20..0xFF - private fun isRunic(c: Char) = runicList.contains(c) - private fun isExtA(c: Char) = c.toInt() in 0x100..0x17F - private fun isExtB(c: Char) = c.toInt() in 0x180..0x24F - private fun isKana(c: Char) = c.toInt() in 0x3040..0x30FF - private fun isCJKPunct(c: Char) = c.toInt() in 0x3000..0x303F - private fun isUniHan(c: Char) = c.toInt() in 0x3400..0x9FFF - private fun isCyrilic(c: Char) = c.toInt() in 0x400..0x45F - private fun isFullwidthUni(c: Char) = c.toInt() in 0xFF00..0xFF1F - private fun isUniPunct(c: Char) = c.toInt() in 0x2000..0x206F - private fun isGreek(c: Char) = c.toInt() in 0x370..0x3CE - private fun isThai(c: Char) = c.toInt() in 0xE00..0xE7F - private fun isThaiDiacritics(c: Char) = c.toInt() in 0xE34..0xE3A - || c.toInt() in 0xE47..0xE4E - || c.toInt() == 0xE31 - private fun isCustomSym(c: Char) = c.toInt() in 0xE000..0xE0FF - - - - private fun extAindexX(c: Char) = (c.toInt() - 0x100) % 16 - private fun extAindexY(c: Char) = (c.toInt() - 0x100) / 16 - - private fun extBindexX(c: Char) = (c.toInt() - 0x180) % 16 - private fun extBindexY(c: Char) = (c.toInt() - 0x180) / 16 - - private fun runicIndexX(c: Char) = runicList.indexOf(c) % 16 - private fun runicIndexY(c: Char) = runicList.indexOf(c) / 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 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 unihanIndexX(c: Char) = (c.toInt() - 0x3400) % 256 - private fun unihanIndexY(c: Char) = (c.toInt() - 0x3400) / 256 - - private fun greekIndexX(c: Char) = (c.toInt() - 0x370) % 16 - private fun greekIndexY(c: Char) = (c.toInt() - 0x370) / 16 - - private fun thaiIndexX(c: Char) = (c.toInt() - 0xE00) % 16 - private fun thaiIndexY(c: Char) = (c.toInt() - 0xE00) / 16 - - private fun symbolIndexX(c: Char) = (c.toInt() - 0xE000) % 16 - private fun symbolIndexY(c: Char) = (c.toInt() - 0xE000) / 16 - - private val unihanWidthSheets = arrayOf( - SHEET_UNIHAN, - SHEET_FW_UNI, - SHEET_UNIHAN - ) - private val variableWidthSheets = arrayOf( - SHEET_ASCII_VARW, - SHEET_CYRILIC_VARW, - SHEET_EXTA_VARW, - SHEET_GREEK_VARW, - SHEET_EXTB_VARW, - SHEET_THAI_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 (variableWidthSheets.contains(ctype)) { - len += try { - glyphWidths[chr.toInt()]!! - } - catch (e: kotlin.KotlinNullPointerException) { - println("KotlinNullPointerException on glyph number ${Integer.toHexString(chr.toInt()).toUpperCase()}") - //System.exit(1) - W_LATIN_WIDE // failsafe - } - } - 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 if (isThaiDiacritics(s[i])) - len += 0 // set width of the glyph as -W_LATIN_WIDE - else if (ctype == SHEET_CUSTOM_SYM) - len += SIZE_KEYCAP - 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 (ch.isColourCode()) { - thisCol = colourKey[ch]!! - continue - } - - 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) - - hangulSheet.getSubImage(indexCho, choRow).drawWithShadow( - Math.round(x + getWidthSubstr(s, i + 1) - W_HANGUL).toFloat(), - Math.round(y).toFloat(), - scale.toFloat(), thisCol, noShadow - ) - hangulSheet.getSubImage(indexJung, jungRow).drawWithShadow( - Math.round(x + getWidthSubstr(s, i + 1) - W_HANGUL).toFloat(), - Math.round(y).toFloat(), - scale.toFloat(), thisCol, noShadow - ) - hangulSheet.getSubImage(indexJong, jongRow).drawWithShadow( - Math.round(x + getWidthSubstr(s, i + 1) - W_HANGUL).toFloat(), - Math.round(y).toFloat(), - scale.toFloat(), thisCol, noShadow - ) - } - } - //hangulSheet.endUse() - - // WenQuanYi - //uniHan.startUse() - - for (i in 0..s.length - 1) { - val ch = s[i] - - if (ch.isColourCode()) { - thisCol = colourKey[ch]!! - continue - } - - if (isUniHan(ch)) { - val glyphW = getWidth("" + ch) - uniHan.getSubImage(unihanIndexX(ch), unihanIndexY(ch)).drawWithShadow( - Math.round(x + getWidthSubstr(s, i + 1) - glyphW).toFloat(), - Math.round((H - H_UNIHAN) / 2 + y).toFloat(), - scale.toFloat(), thisCol, noShadow - ) - } - } - - //uniHan.endUse() - - // regular fonts - var prevInstance = -1 - for (i in 0..s.length - 1) { - val ch = s[i] - - if (ch.isColourCode()) { - thisCol = colourKey[ch]!! - continue - } - - 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_UNIHAN -> { - sheetX = unihanIndexX(ch) - sheetY = unihanIndexY(ch) - } - SHEET_EXTA_VARW -> { - sheetX = extAindexX(ch) - sheetY = extAindexY(ch) - } - SHEET_EXTB_VARW -> { - sheetX = extBindexX(ch) - sheetY = extBindexY(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_THAI_VARW -> { - sheetX = thaiIndexX(ch) - sheetY = thaiIndexY(ch) - } - SHEET_CUSTOM_SYM -> { - sheetX = symbolIndexX(ch) - sheetY = symbolIndexY(ch) - } - 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_CUSTOM_SYM) (H - SIZE_KEYCAP) / 2 // completely legit height adjustment - else 0).toFloat(), - - scale.toFloat(), thisCol, noShadow - ) - } - 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 { - 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 (isExtB(c)) - return SHEET_EXTB_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 (isThai(c)) - return SHEET_THAI_VARW - else if (isCustomSym(c)) - return SHEET_CUSTOM_SYM - 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 Char.isColourCode() = colourKey.containsKey(this) - - fun buildWidthTable(sheet: SpriteSheet, codeOffset: Int, codeRange: IntRange, rows: Int = 16) { - 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) - } - } - - glyphWidths[codeOffset + ccode] = glyphWidth - } - } - - companion object { - - internal val glyphWidths: HashMap = HashMap() - - lateinit internal var hangulSheet: SpriteSheet - lateinit internal var asciiSheet: SpriteSheet - lateinit internal var runicSheet: SpriteSheet - lateinit internal var extASheet: SpriteSheet - lateinit internal var extBSheet: SpriteSheet - lateinit internal var kanaSheet: SpriteSheet - lateinit internal var cjkPunct: SpriteSheet - lateinit internal var uniHan: SpriteSheet - lateinit internal var cyrilic: SpriteSheet - lateinit internal var fullwidthForms: SpriteSheet - lateinit internal var uniPunct: SpriteSheet - lateinit internal var greekSheet: SpriteSheet - lateinit internal var thaiSheet: SpriteSheet - lateinit internal var customSheet: SpriteSheet - - internal val JUNG_COUNT = 21 - internal val JONG_COUNT = 28 - - internal val W_ASIAN_PUNCT = 10 - internal val W_HANGUL = 12 - internal val W_KANA = 12 - internal val W_UNIHAN = 16 - internal val W_LATIN_WIDE = 9 // width of regular letters - - internal val H = 20 - internal val H_UNIHAN = 16 - - internal val SIZE_KEYCAP = 18 - - internal val SHEET_ASCII_VARW = 0 - internal val SHEET_HANGUL = 1 - internal val SHEET_EXTA_VARW = 2 - internal val SHEET_EXTB_VARW = 3 - internal val SHEET_KANA = 4 - internal val SHEET_CJK_PUNCT = 5 - internal val SHEET_UNIHAN = 6 - internal val SHEET_CYRILIC_VARW = 7 - internal val SHEET_FW_UNI = 8 - internal val SHEET_UNI_PUNCT = 9 - internal val SHEET_GREEK_VARW = 10 - internal val SHEET_THAI_VARW = 11 - internal val SHEET_CUSTOM_SYM = 12 - - internal val SHEET_UNKNOWN = 254 - - lateinit internal var sheetKey: Array - - /** - * Runic letters list used for game. The set is - * Younger Futhark + Medieval rune 'e' + Punct + Runic Almanac - - * BEWARE OF SIMILAR-LOOKING RUNES, especially: - - * * Algiz ᛉ instead of Maðr ᛘ - - * * Short-Twig Hagall ᚽ instead of Runic Letter E ᛂ - - * * Runic Letter OE ᚯ instead of Óss ᚬ - - * Examples: - * ᛭ᛋᛁᚴᚱᛁᚦᛦ᛭ - * ᛭ᛂᛚᛋᛅ᛭ᛏᚱᚢᛏᚾᛁᚾᚴᚢᚾᛅ᛬ᛅᚱᚾᛅᛏᛅᛚᛋ - */ - internal val runicList = arrayOf('ᚠ', 'ᚢ', 'ᚦ', 'ᚬ', 'ᚱ', 'ᚴ', 'ᚼ', 'ᚾ', 'ᛁ', 'ᛅ', 'ᛋ', 'ᛏ', 'ᛒ', 'ᛘ', 'ᛚ', 'ᛦ', 'ᛂ', '᛬', '᛫', '᛭', 'ᛮ', 'ᛯ', 'ᛰ') - - var interchar = 0 - var scale = 1 - set(value) { - if (value > 0) field = value - else throw IllegalArgumentException("Font scale cannot be zero or negative (input: $value)") - } - - val colourKey = hashMapOf( - Pair(0x10.toChar(), Color(0xFFFFFF)), //*w hite - Pair(0x11.toChar(), Color(0xFFE080)), //*y ellow - Pair(0x12.toChar(), Color(0xFFB020)), //o range - Pair(0x13.toChar(), Color(0xFF8080)), //*r ed - Pair(0x14.toChar(), Color(0xFFA0E0)), //f uchsia - Pair(0x15.toChar(), Color(0xE0A0FF)), //*m agenta (purple) - Pair(0x16.toChar(), Color(0x8080FF)), //*b lue - Pair(0x17.toChar(), Color(0x80FFFF)), //c yan - Pair(0x18.toChar(), Color(0x80FF80)), //*g reen - Pair(0x19.toChar(), Color(0x008000)), //v iridian - Pair(0x1A.toChar(), Color(0x805030)), //x (khaki) - Pair(0x1B.toChar(), Color(0x808080)) //*k - //* marked: commonly used - ) - val colToCode = hashMapOf( - Pair("w", 0x10.toChar()), - Pair("y", 0x11.toChar()), - Pair("o", 0x12.toChar()), - Pair("r", 0x13.toChar()), - Pair("f", 0x14.toChar()), - Pair("m", 0x15.toChar()), - Pair("b", 0x16.toChar()), - Pair("c", 0x17.toChar()), - Pair("g", 0x18.toChar()), - Pair("v", 0x19.toChar()), - Pair("x", 0x1A.toChar()), - Pair("k", 0x1B.toChar()) - ) - val codeToCol = hashMapOf( - Pair("w", colourKey[0x10.toChar()]), - Pair("y", colourKey[0x11.toChar()]), - Pair("o", colourKey[0x12.toChar()]), - Pair("r", colourKey[0x13.toChar()]), - Pair("f", colourKey[0x14.toChar()]), - Pair("m", colourKey[0x15.toChar()]), - Pair("b", colourKey[0x16.toChar()]), - Pair("c", colourKey[0x17.toChar()]), - Pair("g", colourKey[0x18.toChar()]), - Pair("v", colourKey[0x19.toChar()]), - Pair("x", colourKey[0x1A.toChar()]), - Pair("k", colourKey[0x1B.toChar()]) - ) - }// end of companion object -} - -fun Image.drawWithShadow(x: Float, y: Float, color: Color, noShadow: Boolean) = - this.drawWithShadow(x, y, 1f, color, noShadow) - -fun Image.drawWithShadow(x: Float, y: Float, scale: Float, color: Color, noShadow: Boolean) { - if (!noShadow) { - 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) -} \ No newline at end of file diff --git a/terrarumsansbitmap/slick2d/GameFontImpl.kt b/terrarumsansbitmap/slick2d/GameFontImpl.kt deleted file mode 100644 index e09e284..0000000 --- a/terrarumsansbitmap/slick2d/GameFontImpl.kt +++ /dev/null @@ -1,81 +0,0 @@ -package net.torvald.terrarumsansbitmap.slick2d - -import net.torvald.terrarum.Terrarum - -/** - * Created by minjaesong on 16-01-20. - */ -class GameFontImpl(noShadow: Boolean = false) : GameFontBase(noShadow = noShadow) { - - init { - - GameFontBase.hangulSheet = org.newdawn.slick.SpriteSheet( - "./assets/graphics/fonts/hangul_johab.tga", GameFontBase.W_HANGUL, GameFontBase.H) - GameFontBase.asciiSheet = org.newdawn.slick.SpriteSheet( - "./assets/graphics/fonts/ascii_variable.tga", 15, 19, 1) - GameFontBase.runicSheet = org.newdawn.slick.SpriteSheet( - "./assets/graphics/fonts/futhark.tga", GameFontBase.W_LATIN_WIDE, GameFontBase.H) - GameFontBase.extASheet = org.newdawn.slick.SpriteSheet( - "./assets/graphics/fonts/LatinExtA_variable.tga", 15, 19, 1) - GameFontBase.extBSheet = org.newdawn.slick.SpriteSheet( - "./assets/graphics/fonts/LatinExtB_variable.tga", 15, 19, 1) - GameFontBase.kanaSheet = org.newdawn.slick.SpriteSheet( - "./assets/graphics/fonts/kana.tga", GameFontBase.W_KANA, GameFontBase.H) - GameFontBase.cjkPunct = org.newdawn.slick.SpriteSheet( - "./assets/graphics/fonts/cjkpunct.tga", GameFontBase.W_ASIAN_PUNCT, GameFontBase.H) - GameFontBase.cyrilic = org.newdawn.slick.SpriteSheet( - when (Terrarum.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 = org.newdawn.slick.SpriteSheet( - "./assets/graphics/fonts/fullwidth_forms.tga", GameFontBase.W_UNIHAN, GameFontBase.H_UNIHAN) - GameFontBase.uniPunct = org.newdawn.slick.SpriteSheet( - "./assets/graphics/fonts/unipunct.tga", GameFontBase.W_LATIN_WIDE, GameFontBase.H) - GameFontBase.uniHan = org.newdawn.slick.SpriteSheet( - "./assets/graphics/fonts/wenquanyi.tga.gz", 16, 16) // ~32 MB - GameFontBase.greekSheet = org.newdawn.slick.SpriteSheet( - "./assets/graphics/fonts/greek_variable.tga", 15, 19, 1) - GameFontBase.thaiSheet = org.newdawn.slick.SpriteSheet( - "./assets/graphics/fonts/thai_variable.tga", 15, 19, 1) - GameFontBase.customSheet = org.newdawn.slick.SpriteSheet( - "./assets/graphics/fonts/puae000-e0ff.tga", GameFontBase.SIZE_KEYCAP, GameFontBase.SIZE_KEYCAP) - - val shk = arrayOf( - GameFontBase.asciiSheet, - GameFontBase.hangulSheet, - GameFontBase.runicSheet, - GameFontBase.extASheet, - GameFontBase.extBSheet, - GameFontBase.kanaSheet, - GameFontBase.cjkPunct, - GameFontBase.uniHan, - GameFontBase.cyrilic, - GameFontBase.fullwidthForms, - GameFontBase.uniPunct, - GameFontBase.greekSheet, - GameFontBase.thaiSheet, - null, // Thai EF, filler because not being used right now - GameFontBase.customSheet - ) - GameFontBase.sheetKey = shk - - - buildWidthTable(GameFontBase.asciiSheet, 0, 0..0xFF) - buildWidthTable(GameFontBase.extASheet, 0x100, 0..0x7F) - buildWidthTable(GameFontBase.extBSheet, 0x180, 0..0xCF) - buildWidthTable(GameFontBase.cyrilic, 0x400, 0..0x5F) - buildWidthTable(GameFontBase.greekSheet, 0x370, 0..0x5F) - } - - fun reload() { - GameFontBase.cyrilic.destroy() - GameFontBase.cyrilic = org.newdawn.slick.SpriteSheet( - when (Terrarum.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) - } -}