diff --git a/FontTestGDX/src/TypewriterGDX.kt b/FontTestGDX/src/TypewriterGDX.kt new file mode 100644 index 0000000..05ea0a5 --- /dev/null +++ b/FontTestGDX/src/TypewriterGDX.kt @@ -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 = 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) { + appConfig = Lwjgl3ApplicationConfiguration() + appConfig.useVsync(false) + appConfig.setResizable(false) + appConfig.setWindowedMode(600, 800) + appConfig.setTitle("Terrarum Sans Bitmap Test") + + Lwjgl3Application(TypewriterGDX(600, 800), appConfig) +} diff --git a/assets/typewriter/typewriter_ko_3set-390.tga b/assets/typewriter/typewriter_ko_3set-390.tga new file mode 100644 index 0000000..a32ae6a Binary files /dev/null and b/assets/typewriter/typewriter_ko_3set-390.tga differ diff --git a/src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt b/src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt index f45cc0c..719089e 100755 --- a/src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt +++ b/src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt @@ -33,7 +33,6 @@ import com.badlogic.gdx.utils.GdxRuntimeException import net.torvald.terrarumsansbitmap.GlyphProps import java.io.BufferedOutputStream import java.io.FileOutputStream -import java.util.* import java.util.zip.CRC32 import java.util.zip.GZIPInputStream import kotlin.math.roundToInt @@ -116,29 +115,6 @@ class TerrarumSansBitmap( * 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() - - private data class TextCacheObj(val hash: Long, val glyphLayout: ShittyGlyphLayout?): Comparable { -// 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 val textCache = HashMap(textCacheSize * 2) @@ -190,7 +166,7 @@ class TerrarumSansBitmap( /** Props of all printable Unicode points. */ - private val glyphProps: HashMap = HashMap() + private val glyphProps = HashMap() private val sheets: Array 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 drawNormalised(batch: Batch, codepoints: CodepointSequence, x: Float, y: Float): GlyphLayout? { - if (debug) - println("[TerrarumSansBitmap] max age: $textCacheCap") // Q&D fix for issue #12 // When the line ends with a diacritics, the whole letter won't render @@ -707,7 +681,7 @@ class TerrarumSansBitmap( return intArrayOf(sheetX, sheetY) } - fun buildWidthTable(pixmap: Pixmap, codeRange: Iterable, cols: Int = 16) { + private fun buildWidthTable(pixmap: Pixmap, codeRange: Iterable, cols: Int = 16) { val binaryCodeOffset = W_VAR_INIT val cellW = W_VAR_INIT + 1 @@ -966,7 +940,7 @@ class TerrarumSansBitmap( // fill the last of the posXbuffer if (str.isNotEmpty()) { 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 if (lastCharProp?.writeOnTop == true) { val realDiacriticWidth = if (lastCharProp.alignWhere == GlyphProps.ALIGN_CENTRE) { @@ -1344,6 +1318,26 @@ class TerrarumSansBitmap( } 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 { + fun dispose() { + glyphLayout?.linotype?.dispose() + } + + override fun compareTo(other: TextCacheObj): Int { + return (this.hash - other.hash).sign + } + } + + + private val HCF = 0x115F private val HJF = 0x1160 @@ -1643,7 +1637,7 @@ class TerrarumSansBitmap( private fun isIPA(c: CodePoint) = c in codeRange[SHEET_IPA_VARW] private fun isLatinExtAdd(c: CodePoint) = c in 0x1E00..0x1EFF 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 isCherokee(c: CodePoint) = c in codeRange[SHEET_TSALAGI_VARW] private fun isInsular(c: CodePoint) = c == 0x1D79 diff --git a/src/net/torvald/terrarumtypewriterbitmap/gdx/TerrarumTypewriterBitmap.kt b/src/net/torvald/terrarumtypewriterbitmap/gdx/TerrarumTypewriterBitmap.kt index 11e6d94..6bcdc41 100644 --- a/src/net/torvald/terrarumtypewriterbitmap/gdx/TerrarumTypewriterBitmap.kt +++ b/src/net/torvald/terrarumtypewriterbitmap/gdx/TerrarumTypewriterBitmap.kt @@ -1,8 +1,24 @@ 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 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.util.zip.GZIPInputStream +import kotlin.math.roundToInt /** * Config File Syntax: @@ -35,5 +51,553 @@ class TerrarumTypewriterBitmap( override fun getDescent() = 3f override fun isFlipped() = flipY + var interchar = 0 + private val glyphProps = HashMap() + private val sheets = HashMap() + + private val spriteSheetNames = HashMap() + private val codepointStart = HashMap() + private val codepointToSheetID = HashMap() + + private var textCacheCap = 0 + private val textCache = HashMap(textCacheSize * 2) + private val colourBuffer = HashMap() + + /** + * 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, 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): Pair { + 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() } + } } \ No newline at end of file diff --git a/work_files/typewriter_input/typewriter_input_template.psd b/work_files/typewriter_input/typewriter_input_template.psd index 85a9201..fdcb3fe 100644 Binary files a/work_files/typewriter_input/typewriter_input_template.psd and b/work_files/typewriter_input/typewriter_input_template.psd differ diff --git a/work_files/typewriter_input/typewriter_ko_3set-390.psd b/work_files/typewriter_input/typewriter_ko_3set-390.psd index c9768e3..435a32d 100644 Binary files a/work_files/typewriter_input/typewriter_ko_3set-390.psd and b/work_files/typewriter_input/typewriter_ko_3set-390.psd differ diff --git a/work_files/typewriter_input/typewriter_ko_3set_glyphs_resized.kra b/work_files/typewriter_input/typewriter_ko_3set_glyphs_resized.kra index 3317e65..4aaa26c 100644 Binary files a/work_files/typewriter_input/typewriter_ko_3set_glyphs_resized.kra and b/work_files/typewriter_input/typewriter_ko_3set_glyphs_resized.kra differ