mirror of
https://github.com/curioustorvald/Terrarum-sans-bitmap.git
synced 2026-06-12 08:54:04 +09:00
typewriter font is working but not quite
This commit is contained in:
116
FontTestGDX/src/TypewriterGDX.kt
Normal file
116
FontTestGDX/src/TypewriterGDX.kt
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import com.badlogic.gdx.Game
|
||||||
|
import com.badlogic.gdx.Gdx
|
||||||
|
import com.badlogic.gdx.Input
|
||||||
|
import com.badlogic.gdx.InputAdapter
|
||||||
|
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application
|
||||||
|
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration
|
||||||
|
import com.badlogic.gdx.graphics.Color
|
||||||
|
import com.badlogic.gdx.graphics.GL20
|
||||||
|
import com.badlogic.gdx.graphics.OrthographicCamera
|
||||||
|
import com.badlogic.gdx.graphics.Pixmap
|
||||||
|
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||||
|
import com.badlogic.gdx.graphics.glutils.FrameBuffer
|
||||||
|
import net.torvald.terrarumsansbitmap.gdx.CodepointSequence
|
||||||
|
import net.torvald.terrarumtypewriterbitmap.gdx.TerrarumTypewriterBitmap
|
||||||
|
import java.io.StringReader
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by minjaesong on 2021-11-05.
|
||||||
|
*/
|
||||||
|
class TypewriterGDX(val width: Int, val height: Int) : Game() {
|
||||||
|
|
||||||
|
lateinit var font: TerrarumTypewriterBitmap
|
||||||
|
lateinit var batch: SpriteBatch
|
||||||
|
// lateinit var frameBuffer: FrameBuffer
|
||||||
|
lateinit var camera: OrthographicCamera
|
||||||
|
|
||||||
|
override fun create() {
|
||||||
|
font = TerrarumTypewriterBitmap(
|
||||||
|
"./assets/typewriter",
|
||||||
|
StringReader("ko_kr_3set-390_typewriter,typewriter_ko_3set-390.tga,16"),
|
||||||
|
true, false, 256, true
|
||||||
|
)
|
||||||
|
|
||||||
|
batch = SpriteBatch()
|
||||||
|
|
||||||
|
// frameBuffer = FrameBuffer(Pixmap.Format.RGBA8888, TEXW, TEXH, true)
|
||||||
|
|
||||||
|
camera = OrthographicCamera(width.toFloat(), height.toFloat())
|
||||||
|
camera.translate(width.div(2f), 0f)
|
||||||
|
camera.setToOrtho(true, width.toFloat(), height.toFloat())
|
||||||
|
camera.update()
|
||||||
|
|
||||||
|
|
||||||
|
Gdx.input.inputProcessor = TypewriterInput(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
// uvamr ibwk/f ;rxubnfs
|
||||||
|
private val textbuf: ArrayList<CodepointSequence> = arrayListOf(
|
||||||
|
CodepointSequence(listOf(49,50,29,49,34,29,41,46, 62 ,37,30,51,39,76,34).map { it + 0xF3000 })
|
||||||
|
)
|
||||||
|
|
||||||
|
fun acceptKey(keycode: Int) {
|
||||||
|
println("[TypewriterGDX] Accepting key: $keycode")
|
||||||
|
|
||||||
|
if (keycode == Input.Keys.ENTER) {
|
||||||
|
textbuf.add(CodepointSequence())
|
||||||
|
}
|
||||||
|
else if (keycode and 128 == Input.Keys.BACKSPACE) {
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
textbuf.last().add(keycode + 0xF3000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private val textCol = Color(0.1f,0.1f,0.1f,1f)
|
||||||
|
override fun render() {
|
||||||
|
Gdx.gl.glClearColor(0.97f,0.96f,0.95f,1f)
|
||||||
|
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
|
||||||
|
Gdx.gl.glEnable(GL20.GL_TEXTURE_2D)
|
||||||
|
Gdx.gl.glEnable(GL20.GL_BLEND)
|
||||||
|
Gdx.gl.glBlendFuncSeparate(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA, GL20.GL_ONE, GL20.GL_ONE)
|
||||||
|
|
||||||
|
batch.projectionMatrix = camera.combined
|
||||||
|
batch.begin()
|
||||||
|
|
||||||
|
batch.color = textCol
|
||||||
|
textbuf.forEachIndexed { index, s ->
|
||||||
|
font.draw(batch, s, 40f, 40f + 32*index)
|
||||||
|
}
|
||||||
|
|
||||||
|
batch.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
font.dispose()
|
||||||
|
batch.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TypewriterInput(val main: TypewriterGDX) : InputAdapter() {
|
||||||
|
|
||||||
|
private var shiftIn = false
|
||||||
|
|
||||||
|
override fun keyDown(keycode: Int): Boolean {
|
||||||
|
// FIXME this shiftIn would not work at all...
|
||||||
|
shiftIn = (keycode == Input.Keys.SHIFT_LEFT || keycode == Input.Keys.SHIFT_RIGHT)
|
||||||
|
if (keycode < 128 && keycode != Input.Keys.SHIFT_LEFT && keycode != Input.Keys.SHIFT_RIGHT) {
|
||||||
|
main.acceptKey(shiftIn.toInt() * 128 + keycode)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Boolean.toInt() = if (this) 1 else 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
appConfig = Lwjgl3ApplicationConfiguration()
|
||||||
|
appConfig.useVsync(false)
|
||||||
|
appConfig.setResizable(false)
|
||||||
|
appConfig.setWindowedMode(600, 800)
|
||||||
|
appConfig.setTitle("Terrarum Sans Bitmap Test")
|
||||||
|
|
||||||
|
Lwjgl3Application(TypewriterGDX(600, 800), appConfig)
|
||||||
|
}
|
||||||
BIN
assets/typewriter/typewriter_ko_3set-390.tga
Normal file
BIN
assets/typewriter/typewriter_ko_3set-390.tga
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 320 KiB |
@@ -33,7 +33,6 @@ import com.badlogic.gdx.utils.GdxRuntimeException
|
|||||||
import net.torvald.terrarumsansbitmap.GlyphProps
|
import net.torvald.terrarumsansbitmap.GlyphProps
|
||||||
import java.io.BufferedOutputStream
|
import java.io.BufferedOutputStream
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.util.*
|
|
||||||
import java.util.zip.CRC32
|
import java.util.zip.CRC32
|
||||||
import java.util.zip.GZIPInputStream
|
import java.util.zip.GZIPInputStream
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@@ -116,29 +115,6 @@ class TerrarumSansBitmap(
|
|||||||
* into one sheet and gives custom internal indices)
|
* into one sheet and gives custom internal indices)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* lowercase AND the height is equal to x-height (e.g. lowercase B, D, F, H, K, L, ... does not count
|
|
||||||
*/
|
|
||||||
private fun CodePoint.isLowHeight() = this in lowHeightLetters
|
|
||||||
|
|
||||||
|
|
||||||
// TODO (val posXbuffer: IntArray, val posYbuffer: IntArray) -> (val linotype: Pixmap)
|
|
||||||
private data class ShittyGlyphLayout(val textBuffer: CodepointSequence, val linotype: Texture, val width: Int)
|
|
||||||
|
|
||||||
//private val textCache = HashMap<CharSequence, ShittyGlyphLayout>()
|
|
||||||
|
|
||||||
private data class TextCacheObj(val hash: Long, val glyphLayout: ShittyGlyphLayout?): Comparable<TextCacheObj> {
|
|
||||||
// var disposed = false; private set
|
|
||||||
|
|
||||||
fun dispose() {
|
|
||||||
glyphLayout?.linotype?.dispose()
|
|
||||||
// disposed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun compareTo(other: TextCacheObj): Int {
|
|
||||||
return (this.hash - other.hash).sign
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private var textCacheCap = 0
|
private var textCacheCap = 0
|
||||||
private val textCache = HashMap<Long, TextCacheObj>(textCacheSize * 2)
|
private val textCache = HashMap<Long, TextCacheObj>(textCacheSize * 2)
|
||||||
|
|
||||||
@@ -190,7 +166,7 @@ class TerrarumSansBitmap(
|
|||||||
|
|
||||||
|
|
||||||
/** Props of all printable Unicode points. */
|
/** Props of all printable Unicode points. */
|
||||||
private val glyphProps: HashMap<CodePoint, GlyphProps> = HashMap()
|
private val glyphProps = HashMap<CodePoint, GlyphProps>()
|
||||||
private val sheets: Array<PixmapRegionPack>
|
private val sheets: Array<PixmapRegionPack>
|
||||||
|
|
||||||
private var charsetOverride = 0
|
private var charsetOverride = 0
|
||||||
@@ -362,8 +338,6 @@ class TerrarumSansBitmap(
|
|||||||
fun draw(batch: Batch, codepoints: CodepointSequence, x: Float, y: Float) = drawNormalised(batch, codepoints.normalise(), x, y)
|
fun draw(batch: Batch, codepoints: CodepointSequence, x: Float, y: Float) = drawNormalised(batch, codepoints.normalise(), x, y)
|
||||||
|
|
||||||
fun drawNormalised(batch: Batch, codepoints: CodepointSequence, x: Float, y: Float): GlyphLayout? {
|
fun drawNormalised(batch: Batch, codepoints: CodepointSequence, x: Float, y: Float): GlyphLayout? {
|
||||||
if (debug)
|
|
||||||
println("[TerrarumSansBitmap] max age: $textCacheCap")
|
|
||||||
|
|
||||||
// Q&D fix for issue #12
|
// Q&D fix for issue #12
|
||||||
// When the line ends with a diacritics, the whole letter won't render
|
// When the line ends with a diacritics, the whole letter won't render
|
||||||
@@ -707,7 +681,7 @@ class TerrarumSansBitmap(
|
|||||||
return intArrayOf(sheetX, sheetY)
|
return intArrayOf(sheetX, sheetY)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildWidthTable(pixmap: Pixmap, codeRange: Iterable<Int>, cols: Int = 16) {
|
private fun buildWidthTable(pixmap: Pixmap, codeRange: Iterable<Int>, cols: Int = 16) {
|
||||||
val binaryCodeOffset = W_VAR_INIT
|
val binaryCodeOffset = W_VAR_INIT
|
||||||
|
|
||||||
val cellW = W_VAR_INIT + 1
|
val cellW = W_VAR_INIT + 1
|
||||||
@@ -966,7 +940,7 @@ class TerrarumSansBitmap(
|
|||||||
// fill the last of the posXbuffer
|
// fill the last of the posXbuffer
|
||||||
if (str.isNotEmpty()) {
|
if (str.isNotEmpty()) {
|
||||||
val lastCharProp = glyphProps[str.last()]
|
val lastCharProp = glyphProps[str.last()]
|
||||||
val penultCharProp = glyphProps[nonDiacriticCounter]!!
|
val penultCharProp = glyphProps[str[nonDiacriticCounter]]!!
|
||||||
posXbuffer[posXbuffer.lastIndex] = 1 + posXbuffer[posXbuffer.lastIndex - 1] + // adding 1 to house the shadow
|
posXbuffer[posXbuffer.lastIndex] = 1 + posXbuffer[posXbuffer.lastIndex - 1] + // adding 1 to house the shadow
|
||||||
if (lastCharProp?.writeOnTop == true) {
|
if (lastCharProp?.writeOnTop == true) {
|
||||||
val realDiacriticWidth = if (lastCharProp.alignWhere == GlyphProps.ALIGN_CENTRE) {
|
val realDiacriticWidth = if (lastCharProp.alignWhere == GlyphProps.ALIGN_CENTRE) {
|
||||||
@@ -1344,6 +1318,26 @@ class TerrarumSansBitmap(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lowercase AND the height is equal to x-height (e.g. lowercase B, D, F, H, K, L, ... does not count
|
||||||
|
*/
|
||||||
|
fun CodePoint.isLowHeight() = this in lowHeightLetters
|
||||||
|
|
||||||
|
data class ShittyGlyphLayout(val textBuffer: CodepointSequence, val linotype: Texture, val width: Int)
|
||||||
|
data class TextCacheObj(val hash: Long, val glyphLayout: ShittyGlyphLayout?): Comparable<TextCacheObj> {
|
||||||
|
fun dispose() {
|
||||||
|
glyphLayout?.linotype?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun compareTo(other: TextCacheObj): Int {
|
||||||
|
return (this.hash - other.hash).sign
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private val HCF = 0x115F
|
private val HCF = 0x115F
|
||||||
private val HJF = 0x1160
|
private val HJF = 0x1160
|
||||||
|
|
||||||
@@ -1643,7 +1637,7 @@ class TerrarumSansBitmap(
|
|||||||
private fun isIPA(c: CodePoint) = c in codeRange[SHEET_IPA_VARW]
|
private fun isIPA(c: CodePoint) = c in codeRange[SHEET_IPA_VARW]
|
||||||
private fun isLatinExtAdd(c: CodePoint) = c in 0x1E00..0x1EFF
|
private fun isLatinExtAdd(c: CodePoint) = c in 0x1E00..0x1EFF
|
||||||
private fun isBulgarian(c: CodePoint) = c in 0x400..0x45F
|
private fun isBulgarian(c: CodePoint) = c in 0x400..0x45F
|
||||||
private fun isColourCode(c: CodePoint) = c == 0x100000 || c in 0x10F000..0x10FFFF
|
fun isColourCode(c: CodePoint) = c == 0x100000 || c in 0x10F000..0x10FFFF
|
||||||
private fun isCharsetOverride(c: CodePoint) = c in 0xFFFC0..0xFFFFF
|
private fun isCharsetOverride(c: CodePoint) = c in 0xFFFC0..0xFFFFF
|
||||||
private fun isCherokee(c: CodePoint) = c in codeRange[SHEET_TSALAGI_VARW]
|
private fun isCherokee(c: CodePoint) = c in codeRange[SHEET_TSALAGI_VARW]
|
||||||
private fun isInsular(c: CodePoint) = c == 0x1D79
|
private fun isInsular(c: CodePoint) = c == 0x1D79
|
||||||
|
|||||||
@@ -1,8 +1,24 @@
|
|||||||
package net.torvald.terrarumtypewriterbitmap.gdx
|
package net.torvald.terrarumtypewriterbitmap.gdx
|
||||||
|
|
||||||
|
import com.badlogic.gdx.Gdx
|
||||||
|
import com.badlogic.gdx.graphics.Pixmap
|
||||||
|
import com.badlogic.gdx.graphics.Texture
|
||||||
|
import com.badlogic.gdx.graphics.g2d.Batch
|
||||||
import com.badlogic.gdx.graphics.g2d.BitmapFont
|
import com.badlogic.gdx.graphics.g2d.BitmapFont
|
||||||
import java.io.File
|
import com.badlogic.gdx.graphics.g2d.GlyphLayout
|
||||||
|
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||||
|
import com.badlogic.gdx.utils.GdxRuntimeException
|
||||||
|
import net.torvald.terrarumsansbitmap.GlyphProps
|
||||||
|
import net.torvald.terrarumsansbitmap.gdx.*
|
||||||
|
import net.torvald.terrarumsansbitmap.gdx.CodePoint
|
||||||
|
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap.Companion.TextCacheObj
|
||||||
|
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap.Companion.ShittyGlyphLayout
|
||||||
|
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap.Companion.isLowHeight
|
||||||
|
import java.io.BufferedOutputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
import java.io.Reader
|
import java.io.Reader
|
||||||
|
import java.util.zip.GZIPInputStream
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Config File Syntax:
|
* Config File Syntax:
|
||||||
@@ -35,5 +51,553 @@ class TerrarumTypewriterBitmap(
|
|||||||
override fun getDescent() = 3f
|
override fun getDescent() = 3f
|
||||||
override fun isFlipped() = flipY
|
override fun isFlipped() = flipY
|
||||||
|
|
||||||
|
var interchar = 0
|
||||||
|
|
||||||
|
private val glyphProps = HashMap<CodePoint, GlyphProps>()
|
||||||
|
private val sheets = HashMap<String, PixmapRegionPack>()
|
||||||
|
|
||||||
|
private val spriteSheetNames = HashMap<String, String>()
|
||||||
|
private val codepointStart = HashMap<String, CodePoint>()
|
||||||
|
private val codepointToSheetID = HashMap<Int, String>()
|
||||||
|
|
||||||
|
private var textCacheCap = 0
|
||||||
|
private val textCache = HashMap<Long, TextCacheObj>(textCacheSize * 2)
|
||||||
|
private val colourBuffer = HashMap<CodePoint, ARGB8888>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insertion sorts the last element fo the textCache
|
||||||
|
*/
|
||||||
|
private fun addToCache(text: CodepointSequence, linotype: Texture, width: Int) {
|
||||||
|
val cacheObj =
|
||||||
|
TextCacheObj(text.getHash(), ShittyGlyphLayout(text, linotype, width))
|
||||||
|
|
||||||
|
if (textCacheCap < textCacheSize) {
|
||||||
|
textCache[cacheObj.hash] = cacheObj
|
||||||
|
textCacheCap += 1
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// randomly eliminate one
|
||||||
|
textCache.remove(textCache.keys.random())!!.dispose()
|
||||||
|
|
||||||
|
// add new one
|
||||||
|
textCache[cacheObj.hash] = cacheObj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCache(hash: Long): TextCacheObj? {
|
||||||
|
return textCache[hash]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun getColour(codePoint: Int): Int { // input: 0x10F_RGB, out: RGBA8888
|
||||||
|
if (colourBuffer.containsKey(codePoint))
|
||||||
|
return colourBuffer[codePoint]!!
|
||||||
|
|
||||||
|
val r = codePoint.and(0x0F00).ushr(8)
|
||||||
|
val g = codePoint.and(0x00F0).ushr(4)
|
||||||
|
val b = codePoint.and(0x000F)
|
||||||
|
|
||||||
|
val col = r.shl(28) or r.shl(24) or
|
||||||
|
g.shl(20) or g.shl(16) or
|
||||||
|
b.shl(12) or b.shl(8) or
|
||||||
|
0xFF
|
||||||
|
|
||||||
|
|
||||||
|
colourBuffer[codePoint] = col
|
||||||
|
return col
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
val fontParentDir = if (fontDir.endsWith('/') || fontDir.endsWith('\\')) fontDir else "$fontDir/"
|
||||||
|
|
||||||
|
configFile.forEachLine {
|
||||||
|
if (!it.startsWith("#")) {
|
||||||
|
val csv = it.split(',')
|
||||||
|
if (csv.size != 3) throw IllegalArgumentException("Malformed CSV line: '$it'")
|
||||||
|
val key = csv[0]
|
||||||
|
val sheetname = csv[1]
|
||||||
|
val cpstart = csv[2].toInt() * 256 + 0xF2000
|
||||||
|
|
||||||
|
spriteSheetNames[key] = sheetname
|
||||||
|
codepointStart[key] = cpstart
|
||||||
|
|
||||||
|
for (k in cpstart until cpstart + 256) {
|
||||||
|
codepointToSheetID[k] = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spriteSheetNames.forEach { key, filename ->
|
||||||
|
var pixmap: Pixmap
|
||||||
|
|
||||||
|
println("[TerrarumTypewriterBitmap] loading texture $filename [VARIABLE]")
|
||||||
|
|
||||||
|
// unpack gz if applicable
|
||||||
|
if (filename.endsWith(".gz")) {
|
||||||
|
val tmpFileName = "tmp_${filename.dropLast(7)}.tga"
|
||||||
|
|
||||||
|
try {
|
||||||
|
val gzi = GZIPInputStream(Gdx.files.internal(fontParentDir + filename).read(8192))
|
||||||
|
val wholeFile = gzi.readBytes()
|
||||||
|
gzi.close()
|
||||||
|
val fos = BufferedOutputStream(FileOutputStream(tmpFileName))
|
||||||
|
fos.write(wholeFile)
|
||||||
|
fos.flush()
|
||||||
|
fos.close()
|
||||||
|
|
||||||
|
pixmap = Pixmap(Gdx.files.internal(tmpFileName))
|
||||||
|
}
|
||||||
|
catch (e: GdxRuntimeException) {
|
||||||
|
//e.printStackTrace()
|
||||||
|
System.err.println("[TerrarumTypewriterBitmap] said texture not found, skipping...")
|
||||||
|
|
||||||
|
pixmap = Pixmap(1, 1, Pixmap.Format.RGBA8888)
|
||||||
|
}
|
||||||
|
//File(tmpFileName).delete()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pixmap = try {
|
||||||
|
Pixmap(Gdx.files.internal(fontParentDir + filename))
|
||||||
|
} catch (e: GdxRuntimeException) {
|
||||||
|
//e.printStackTrace()
|
||||||
|
System.err.println("[TerrarumTypewriterBitmap] said texture not found, skipping...")
|
||||||
|
|
||||||
|
Pixmap(1, 1, Pixmap.Format.RGBA8888)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val cpstart = codepointStart[key]!!
|
||||||
|
buildWidthTable(pixmap, cpstart until cpstart + 256, 16)
|
||||||
|
|
||||||
|
val texRegPack = PixmapRegionPack(pixmap,
|
||||||
|
TerrarumSansBitmap.W_VAR_INIT,
|
||||||
|
TerrarumSansBitmap.H,
|
||||||
|
TerrarumSansBitmap.HGAP_VAR, 0
|
||||||
|
)
|
||||||
|
|
||||||
|
sheets[key] = texRegPack
|
||||||
|
|
||||||
|
pixmap.dispose() // you are terminated
|
||||||
|
}
|
||||||
|
|
||||||
|
glyphProps[0] = GlyphProps(0, 0)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSheetType(c: CodePoint) = codepointToSheetID[c] ?: "unknown"
|
||||||
|
private fun getSheetwisePosition(cPrev: Int, ch: Int) = getSheetType(ch).let {
|
||||||
|
val coff = ch - (codepointStart[it] ?: 0)
|
||||||
|
intArrayOf(coff % 16, coff / 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun buildWidthTable(pixmap: Pixmap, codeRange: Iterable<Int>, cols: Int = 16) {
|
||||||
|
val binaryCodeOffset = TerrarumSansBitmap.W_VAR_INIT
|
||||||
|
|
||||||
|
val cellW = TerrarumSansBitmap.W_VAR_INIT + 1
|
||||||
|
val cellH = TerrarumSansBitmap.H
|
||||||
|
|
||||||
|
for (code in codeRange) {
|
||||||
|
|
||||||
|
val cellX = ((code - codeRange.first()) % cols) * cellW
|
||||||
|
val cellY = ((code - codeRange.first()) / cols) * cellH
|
||||||
|
|
||||||
|
val codeStartX = cellX + binaryCodeOffset
|
||||||
|
val codeStartY = cellY
|
||||||
|
val tagStartY = codeStartY + 10
|
||||||
|
|
||||||
|
var width = 0
|
||||||
|
var tags = 0
|
||||||
|
|
||||||
|
for (y in 0..3) {
|
||||||
|
// if ALPHA is not zero, assume it's 1
|
||||||
|
if (pixmap.getPixel(codeStartX, codeStartY + y).and(0xFF) != 0) {
|
||||||
|
width = width or (1 shl y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (y in 0..9) {
|
||||||
|
// if ALPHA is not zero, assume it's 1
|
||||||
|
if (pixmap.getPixel(codeStartX, tagStartY + y).and(0xFF) != 0) {
|
||||||
|
tags = tags or (1 shl y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debug) println("${code.charInfo()}: Width $width, tags $tags")
|
||||||
|
|
||||||
|
/*val isDiacritics = pixmap.getPixel(codeStartX, codeStartY + H - 1).and(0xFF) != 0
|
||||||
|
if (isDiacritics)
|
||||||
|
glyphWidth = -glyphWidth*/
|
||||||
|
|
||||||
|
glyphProps[code] = GlyphProps(width, tags)
|
||||||
|
|
||||||
|
// extra info
|
||||||
|
val extCount = glyphProps[code]?.requiredExtInfoCount() ?: 0
|
||||||
|
if (extCount > 0) {
|
||||||
|
|
||||||
|
glyphProps[code]?.extInfo = IntArray(extCount)
|
||||||
|
|
||||||
|
for (x in 0 until extCount) {
|
||||||
|
var info = 0
|
||||||
|
for (y in 0..18) {
|
||||||
|
// if ALPHA is not zero, assume it's 1
|
||||||
|
if (pixmap.getPixel(cellX + x, cellY + y).and(0xFF) != 0) {
|
||||||
|
info = info or (1 shl y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glyphProps[code]!!.extInfo!![x] = info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val pixmapOffsetY = 10
|
||||||
|
private var flagFirstRun = true
|
||||||
|
private var textBuffer = CodepointSequence(256)
|
||||||
|
private lateinit var tempLinotype: Texture
|
||||||
|
private var nullProp = GlyphProps(15, 0)
|
||||||
|
|
||||||
|
|
||||||
|
fun draw(batch: Batch, codepoints: CodepointSequence, x: Float, y: Float): GlyphLayout? {
|
||||||
|
// Q&D fix for issue #12
|
||||||
|
// When the line ends with a diacritics, the whole letter won't render
|
||||||
|
// If the line starts with a letter-with-diacritic, it will error out
|
||||||
|
// Some diacritics (e.g. COMBINING TILDE) do not obey lowercase letters
|
||||||
|
val charSeqNotBlank = codepoints.size > 0 // determine emptiness BEFORE you hack a null chars in
|
||||||
|
val newCodepoints = CodepointSequence()
|
||||||
|
newCodepoints.add(0)
|
||||||
|
newCodepoints.addAll(codepoints)
|
||||||
|
newCodepoints.add(0)
|
||||||
|
|
||||||
|
fun Int.flipY() = this * if (flipY) 1 else -1
|
||||||
|
|
||||||
|
// always draw at integer position; this is bitmap font after all
|
||||||
|
val x = Math.round(x)
|
||||||
|
val y = Math.round(y)
|
||||||
|
|
||||||
|
val charSeqHash = newCodepoints.getHash()
|
||||||
|
|
||||||
|
var renderCol = -1 // subject to change with the colour code
|
||||||
|
|
||||||
|
if (charSeqNotBlank) {
|
||||||
|
|
||||||
|
val cacheObj = getCache(charSeqHash)
|
||||||
|
|
||||||
|
if (cacheObj == null || flagFirstRun) {
|
||||||
|
textBuffer = newCodepoints
|
||||||
|
|
||||||
|
val (posXbuffer, posYbuffer) = buildPosMap(textBuffer)
|
||||||
|
|
||||||
|
flagFirstRun = false
|
||||||
|
|
||||||
|
//println("text not in buffer: $charSeq")
|
||||||
|
|
||||||
|
|
||||||
|
//textBuffer.forEach { print("${it.toHex()} ") }
|
||||||
|
//println()
|
||||||
|
|
||||||
|
|
||||||
|
// resetHash(charSeq, x.toFloat(), y.toFloat())
|
||||||
|
|
||||||
|
|
||||||
|
val _pw = posXbuffer.last()
|
||||||
|
val _ph = TerrarumSansBitmap.H + (pixmapOffsetY * 2)
|
||||||
|
if (_pw < 0 || _ph < 0) throw RuntimeException("Illegal linotype dimension (w: $_pw, h: $_ph)")
|
||||||
|
val linotypePixmap = Pixmap(_pw, _ph, Pixmap.Format.RGBA8888)
|
||||||
|
|
||||||
|
|
||||||
|
var index = 0
|
||||||
|
while (index <= textBuffer.lastIndex) {
|
||||||
|
val c = textBuffer[index]
|
||||||
|
val sheetID = getSheetType(c)
|
||||||
|
val (sheetX, sheetY) =
|
||||||
|
if (index == 0) getSheetwisePosition(0, c)
|
||||||
|
else getSheetwisePosition(textBuffer[index - 1], c)
|
||||||
|
val hash = getHash(c) // to be used to simulate printing irregularity
|
||||||
|
|
||||||
|
if (TerrarumSansBitmap.isColourCode(c)) {
|
||||||
|
if (c == 0x100000) {
|
||||||
|
renderCol = -1
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
renderCol = getColour(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
val posY = posYbuffer[index].flipY()
|
||||||
|
val posX = posXbuffer[index]
|
||||||
|
val texture = sheets[sheetID]?.get(sheetX, sheetY)
|
||||||
|
|
||||||
|
texture?.let {
|
||||||
|
linotypePixmap.drawPixmap(it, posX, posY + pixmapOffsetY, renderCol)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (noSuchGlyph: ArrayIndexOutOfBoundsException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
|
||||||
|
tempLinotype = Texture(linotypePixmap)
|
||||||
|
tempLinotype.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
|
||||||
|
|
||||||
|
// put things into cache
|
||||||
|
//textCache[charSeq] = ShittyGlyphLayout(textBuffer, linotype!!)
|
||||||
|
addToCache(textBuffer, tempLinotype, posXbuffer.last())
|
||||||
|
linotypePixmap.dispose()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
textBuffer = cacheObj.glyphLayout!!.textBuffer
|
||||||
|
tempLinotype = cacheObj.glyphLayout!!.linotype
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!flipY) {
|
||||||
|
batch.draw(tempLinotype, x.toFloat(), (y - pixmapOffsetY).toFloat())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
batch.draw(tempLinotype,
|
||||||
|
x.toFloat(),
|
||||||
|
(y - pixmapOffsetY + (tempLinotype.height)).toFloat(),
|
||||||
|
(tempLinotype.width.toFloat()),
|
||||||
|
-(tempLinotype.height.toFloat())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* posXbuffer's size is greater than the string, last element marks the width of entire string.
|
||||||
|
*/
|
||||||
|
private fun buildPosMap(str: List<Int>): Pair<IntArray, IntArray> {
|
||||||
|
val posXbuffer = IntArray(str.size + 1) { 0 }
|
||||||
|
val posYbuffer = IntArray(str.size) { 0 }
|
||||||
|
|
||||||
|
|
||||||
|
var nonDiacriticCounter = 0 // index of last instance of non-diacritic char
|
||||||
|
var stackUpwardCounter = 0
|
||||||
|
var stackDownwardCounter = 0
|
||||||
|
|
||||||
|
val HALF_VAR_INIT = TerrarumSansBitmap.W_VAR_INIT.minus(1).div(2)
|
||||||
|
|
||||||
|
// this is starting to get dirty...
|
||||||
|
// persisting value. the value is set a few characters before the actual usage
|
||||||
|
var extraWidth = 0
|
||||||
|
|
||||||
|
for (charIndex in 0 until posXbuffer.size - 1) {
|
||||||
|
if (charIndex > 0) {
|
||||||
|
// nonDiacriticCounter allows multiple diacritics
|
||||||
|
|
||||||
|
val thisChar = str[charIndex]
|
||||||
|
if (glyphProps[thisChar] == null && errorOnUnknownChar) {
|
||||||
|
val errorGlyphSB = StringBuilder()
|
||||||
|
Character.toChars(thisChar).forEach { errorGlyphSB.append(it) }
|
||||||
|
|
||||||
|
throw InternalError("No GlyphProps for char '$errorGlyphSB' " +
|
||||||
|
"(${thisChar.charInfo()})")
|
||||||
|
}
|
||||||
|
val thisProp = glyphProps[thisChar] ?: nullProp
|
||||||
|
val lastNonDiacriticChar = str[nonDiacriticCounter]
|
||||||
|
val itsProp = glyphProps[lastNonDiacriticChar] ?: nullProp
|
||||||
|
val kerning = 0
|
||||||
|
|
||||||
|
|
||||||
|
//println("char: ${thisChar.charInfo()}\nproperties: $thisProp")
|
||||||
|
|
||||||
|
|
||||||
|
var alignmentOffset = when (thisProp.alignWhere) {
|
||||||
|
GlyphProps.ALIGN_LEFT -> 0
|
||||||
|
GlyphProps.ALIGN_RIGHT -> thisProp.width - TerrarumSansBitmap.W_VAR_INIT
|
||||||
|
GlyphProps.ALIGN_CENTRE -> Math.ceil((thisProp.width - TerrarumSansBitmap.W_VAR_INIT) / 2.0).toInt()
|
||||||
|
else -> 0 // implies "diacriticsBeforeGlyph = true"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!thisProp.writeOnTop) {
|
||||||
|
posXbuffer[charIndex] = when (itsProp.alignWhere) {
|
||||||
|
GlyphProps.ALIGN_RIGHT ->
|
||||||
|
posXbuffer[nonDiacriticCounter] + TerrarumSansBitmap.W_VAR_INIT + alignmentOffset + interchar + kerning + extraWidth
|
||||||
|
GlyphProps.ALIGN_CENTRE ->
|
||||||
|
posXbuffer[nonDiacriticCounter] + HALF_VAR_INIT + itsProp.width + alignmentOffset + interchar + kerning + extraWidth
|
||||||
|
else ->
|
||||||
|
posXbuffer[nonDiacriticCounter] + itsProp.width + alignmentOffset + interchar + kerning + extraWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
nonDiacriticCounter = charIndex
|
||||||
|
|
||||||
|
stackUpwardCounter = 0
|
||||||
|
stackDownwardCounter = 0
|
||||||
|
extraWidth = 0
|
||||||
|
}
|
||||||
|
else if (thisProp.writeOnTop && thisProp.alignXPos == GlyphProps.DIA_JOINER) {
|
||||||
|
posXbuffer[charIndex] = when (itsProp.alignWhere) {
|
||||||
|
GlyphProps.ALIGN_RIGHT ->
|
||||||
|
posXbuffer[nonDiacriticCounter] + TerrarumSansBitmap.W_VAR_INIT + alignmentOffset
|
||||||
|
//GlyphProps.ALIGN_CENTRE ->
|
||||||
|
// posXbuffer[nonDiacriticCounter] + HALF_VAR_INIT + itsProp.width + alignmentOffset
|
||||||
|
else ->
|
||||||
|
posXbuffer[nonDiacriticCounter] + itsProp.width + alignmentOffset
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// set X pos according to alignment information
|
||||||
|
posXbuffer[charIndex] = when (thisProp.alignWhere) {
|
||||||
|
GlyphProps.ALIGN_LEFT, GlyphProps.ALIGN_BEFORE -> posXbuffer[nonDiacriticCounter]
|
||||||
|
GlyphProps.ALIGN_RIGHT -> {
|
||||||
|
posXbuffer[nonDiacriticCounter] - (TerrarumSansBitmap.W_VAR_INIT - itsProp.width)
|
||||||
|
}
|
||||||
|
GlyphProps.ALIGN_CENTRE -> {
|
||||||
|
val alignXPos = if (itsProp.alignXPos == 0) itsProp.width.div(2) else itsProp.alignXPos
|
||||||
|
|
||||||
|
if (itsProp.alignWhere == GlyphProps.ALIGN_RIGHT) {
|
||||||
|
posXbuffer[nonDiacriticCounter] + alignXPos + (itsProp.width + 1).div(2)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
posXbuffer[nonDiacriticCounter] + alignXPos - HALF_VAR_INIT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw InternalError("Unsupported alignment: ${thisProp.alignWhere}")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// set Y pos according to diacritics position
|
||||||
|
if (thisProp.alignWhere == GlyphProps.ALIGN_CENTRE) {
|
||||||
|
when (thisProp.stackWhere) {
|
||||||
|
GlyphProps.STACK_DOWN -> {
|
||||||
|
posYbuffer[charIndex] = TerrarumSansBitmap.H_DIACRITICS * stackDownwardCounter
|
||||||
|
stackDownwardCounter++
|
||||||
|
}
|
||||||
|
GlyphProps.STACK_UP -> {
|
||||||
|
posYbuffer[charIndex] = -TerrarumSansBitmap.H_DIACRITICS * stackUpwardCounter
|
||||||
|
|
||||||
|
// shift down on lowercase if applicable
|
||||||
|
/*if (getSheetType(thisChar) in TerrarumSansBitmap.autoShiftDownOnLowercase &&
|
||||||
|
lastNonDiacriticChar.isLowHeight()) {
|
||||||
|
//println("AAARRRRHHHH for character ${thisChar.toHex()}")
|
||||||
|
//println("lastNonDiacriticChar: ${lastNonDiacriticChar.toHex()}")
|
||||||
|
//println("cond: ${thisProp.alignXPos == GlyphProps.DIA_OVERLAY}, charIndex: $charIndex")
|
||||||
|
if (thisProp.alignXPos == GlyphProps.DIA_OVERLAY)
|
||||||
|
posYbuffer[charIndex] -= TerrarumSansBitmap.H_OVERLAY_LOWERCASE_SHIFTDOWN // if minus-assign doesn't work, try plus-assign
|
||||||
|
else
|
||||||
|
posYbuffer[charIndex] -= TerrarumSansBitmap.H_STACKUP_LOWERCASE_SHIFTDOWN // if minus-assign doesn't work, try plus-assign
|
||||||
|
}*/
|
||||||
|
|
||||||
|
stackUpwardCounter++
|
||||||
|
}
|
||||||
|
GlyphProps.STACK_UP_N_DOWN -> {
|
||||||
|
posYbuffer[charIndex] = TerrarumSansBitmap.H_DIACRITICS * stackDownwardCounter
|
||||||
|
stackDownwardCounter++
|
||||||
|
posYbuffer[charIndex] = -TerrarumSansBitmap.H_DIACRITICS * stackUpwardCounter
|
||||||
|
stackUpwardCounter++
|
||||||
|
}
|
||||||
|
// for BEFORE_N_AFTER, do nothing in here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill the last of the posXbuffer
|
||||||
|
if (str.isNotEmpty()) {
|
||||||
|
val lastCharProp = glyphProps[str.last()]
|
||||||
|
val penultCharProp = glyphProps[str[nonDiacriticCounter]]!!
|
||||||
|
posXbuffer[posXbuffer.lastIndex] = 1 + posXbuffer[posXbuffer.lastIndex - 1] + // adding 1 to house the shadow
|
||||||
|
if (lastCharProp?.writeOnTop == true) {
|
||||||
|
val realDiacriticWidth = if (lastCharProp.alignWhere == GlyphProps.ALIGN_CENTRE) {
|
||||||
|
(lastCharProp.width).div(2) + penultCharProp.alignXPos
|
||||||
|
}
|
||||||
|
else if (lastCharProp.alignWhere == GlyphProps.ALIGN_RIGHT) {
|
||||||
|
(lastCharProp.width) + penultCharProp.alignXPos
|
||||||
|
}
|
||||||
|
else 0
|
||||||
|
|
||||||
|
maxOf(penultCharProp.width, realDiacriticWidth)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
(lastCharProp?.width ?: 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
posXbuffer[0] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return posXbuffer to posYbuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @param col RGBA8888 representation
|
||||||
|
*/
|
||||||
|
private fun Pixmap.drawPixmap(pixmap: Pixmap, xPos: Int, yPos: Int, col: Int) {
|
||||||
|
for (y in 0 until pixmap.height) {
|
||||||
|
for (x in 0 until pixmap.width) {
|
||||||
|
val pixel = pixmap.getPixel(x, y) // Pixmap uses RGBA8888, while Color uses ARGB. What the fuck?
|
||||||
|
|
||||||
|
val newPixel = pixel colorTimes col
|
||||||
|
|
||||||
|
this.drawPixel(xPos + x, yPos + y, newPixel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private infix fun Int.colorTimes(other: Int): Int {
|
||||||
|
val thisBytes = IntArray(4) { this.ushr(it * 8).and(255) }
|
||||||
|
val otherBytes = IntArray(4) { other.ushr(it * 8).and(255) }
|
||||||
|
|
||||||
|
return (thisBytes[0] times256 otherBytes[0]) or
|
||||||
|
(thisBytes[1] times256 otherBytes[1]).shl(8) or
|
||||||
|
(thisBytes[2] times256 otherBytes[2]).shl(16) or
|
||||||
|
(thisBytes[3] times256 otherBytes[3]).shl(24)
|
||||||
|
}
|
||||||
|
|
||||||
|
private infix fun Int.times256(other: Int) = multTable255[this][other]
|
||||||
|
|
||||||
|
private val multTable255 = Array(256) { left ->
|
||||||
|
IntArray(256) { right ->
|
||||||
|
(255f * (left / 255f).times(right / 255f)).roundToInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// randomiser effect hash ONLY
|
||||||
|
private val hashBasis = -3750763034362895579L
|
||||||
|
private val hashPrime = 1099511628211L
|
||||||
|
private var hashAccumulator = hashBasis
|
||||||
|
fun getHash(char: Int): Long {
|
||||||
|
hashAccumulator = hashAccumulator xor char.toLong()
|
||||||
|
hashAccumulator *= hashPrime
|
||||||
|
return hashAccumulator
|
||||||
|
}
|
||||||
|
|
||||||
|
fun CodepointSequence.getHash(): Long {
|
||||||
|
val hashBasis = -3750763034362895579L
|
||||||
|
val hashPrime = 1099511628211L
|
||||||
|
var hashAccumulator = hashBasis
|
||||||
|
|
||||||
|
this.forEach {
|
||||||
|
hashAccumulator = hashAccumulator xor it.toLong()
|
||||||
|
hashAccumulator *= hashPrime
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashAccumulator
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Int.charInfo() = "U+${this.toString(16).padStart(4, '0').toUpperCase()}: ${Character.getName(this)}"
|
||||||
|
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
super.dispose()
|
||||||
|
textCache.values.forEach { it.dispose() }
|
||||||
|
sheets.values.forEach { it.dispose() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user