diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2a41dea..a20314c 100755 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,19 +38,29 @@ Width is encoded in binary bits, on pixels. On the font spritesheet, every glyph ### Glyph Tags -Green-tinted area (should be 10 px tall) contains the tags. Tags are defined as following: +Rightmost vertical column (should be 20 px tall) contains the tags. Tags are defined as following: ``` -(LSB) 0 == Use Compiler Directive (Undefined right now, keep it as 0) - 1 -, 1 = Align to this X pos of prev char, - 1 | 2 = only valid if write-on-top is 1 - 1 | 4 = and is centre-aligned and non-zero - 1 -' 8 = (if this is zero, floorOf(width/2) will be used instead) - 0 -, 0 Align 1 Align 0 Align 1 Align before - 1 -' 0 left 0 right 1 centre 1 the glyph - 1 == write-on-top, usually it's diatritics but not always (e.g. devanagari vowel sign O) - 1 -, 0 Stack 1 Stack 0 Before 1 Up & -(MSB) 0 -' 0 up 0 down 1 &After 1 Down (e.g. U+0C48) +(LSB) W -, + W | + W |= Width of the character + W | + W -' + m --Is this character lowheight? + K -, + K |= Tags used by the "Keming Machine" + K | + K -' ,-Unused + · --' + X -, Align to this X pos of prev char, only valid if write-on-top is 1 + X |= and is centre-aligned and non-zero + X | (if this is zero, floorOf(width/2) will be used instead) + X -' + A -,_ 0 Align 1 Align 0 Align 1 Align before + A -' 0 left 0 right 1 centre 1 the glyph + D --write-on-top, usually it's diatritics but not always (e.g. devanagari vowel sign O) + S -,_ 0 Stack 1 Stack 0 Before 1 Up & +(MSB) S -' 0 up 0 down 1 &After 1 Down (e.g. U+0C48) ``` #### Stack Up/Down diff --git a/assets/ascii_variable.tga b/assets/ascii_variable.tga index 77d9235..b0a6e3b 100755 Binary files a/assets/ascii_variable.tga and b/assets/ascii_variable.tga differ diff --git a/assets/insular_variable.tga b/assets/insular_variable.tga index 73d4130..71162af 100755 Binary files a/assets/insular_variable.tga and b/assets/insular_variable.tga differ diff --git a/assets/latinExtD_variable.tga b/assets/latinExtD_variable.tga index a2b8e8a..9ce37fa 100644 Binary files a/assets/latinExtD_variable.tga and b/assets/latinExtD_variable.tga differ diff --git a/demo.PNG b/demo.PNG index c6c757d..c70758d 100755 Binary files a/demo.PNG and b/demo.PNG differ diff --git a/demotext.txt b/demotext.txt index 3175f9d..3c77a59 100755 --- a/demotext.txt +++ b/demotext.txt @@ -5,7 +5,8 @@ There are many bitmap fonts on the internet. You care for the multilingual suppo most of them do not support your language, vector fonts take too much time to load, and even then their legibility suffers because screw built-in antialias. -You somehow found a multilingual one, and it makes your game look like an old computer, and you say: +You somehow found a multilingual one, and it makes your text as if they came straight from an old +computer terminal, and you say: “Well, better than nothing… no, it’s ugly.” diff --git a/glyph_height_pos_annotation.png b/glyph_height_pos_annotation.png index 1f82ab4..1e6585e 100755 Binary files a/glyph_height_pos_annotation.png and b/glyph_height_pos_annotation.png differ diff --git a/keming_machine.txt b/keming_machine.txt new file mode 100644 index 0000000..c687c34 --- /dev/null +++ b/keming_machine.txt @@ -0,0 +1,115 @@ +--- Pixel 0 +- Lowheight bit +- encoding: has pixel - it's low height +- used by the diacritics system to quickly look up if the character is low height without parsing the Pixel 1 + +### Legends +# +# ͞ A·B < unset for lowheight miniscules, as in e +# |·| < space we don't care +#――C·D < middle hole for majuscules, as in C +# E·F < middle hole for miniscules, as in c +# ͟ G·H +# ――― < baseline +# |·| +# J·K + +--- Pixel 1 +- A..K Occupied (1024) +- Is ABGH are all Ys instead of Bars? (2) + - Say, A is Bar but E is wye (e.g. Ꮨ), this condition is false; this character must be encoded as ABDFGH(B). +- encoding: + - Y0000000 JK000000 ABCDEFGH + - Y: Bar/Wye Mode + - A..K: arguments +- B-type will contract the space by 2 pixels, while Y-type will do it by 1 + +# Capital/lower itself is given using the pixel 0 due to the diacritics processing + +--- Examples +- AB(B): T +- ABCEGH(B): C +- ABCEFGH(Y): K +- ABCDEG: Ꮅ +- ABCDEFGH: B,D,O +- ABCDFH: Ч +- ABCEG: Г +- ABGH: Ꮖ +- ACDEG: Ꮀ +- ACDEFGH: h,Ƅ +- ACDFH: ߆ +- ACEGH: L +- AH(Y): \ +- BDEFGH: J +- BDFGH: ɺ,ป +- BG(Y): / +- CD: Ⴕ +- CDEF(Y): Φ +- CDEFGH: a,c,e,i,o,φ,ϕ +- CDEFGHJK: g +- CDEFGHK: ƞ + +- AB(Y): Y +- ABCD(Y): V +- CDEF(Y): v +- EFGH(Y): ʌ +- CDGH(Y): A + +--- Rules +# Legend: _ dont care +# @ must have a bit set +# ` must have a bit unset +- ͟A͟B͟C͟D͟E͟F͟G͟H͟J͟K͟ ͟ ͟ ͟A͟B͟C͟D͟E͟F͟G͟H͟J͟K͟ +- _@_`___`__ — `_________ # Γe,TJ ; Ye,YJ,Ve,VJ,TA,ΓA,VA,Vʌ,YA,Yʌ,yA,yʌ,/a,/d +- _@_@___`__ — `___`_@___ # Pɺ but NOT PJ +- ___`_`____ — `___@_`___ # Cꟶ,Kꟶ,Lꟶ,Γꟶ +- ___`_`____ — `_@___`___ # CꟵ,KꟵ,LꟵ,ΓꟵ +----------------------------------------------------- +- _`________ — @_`___`___ # eꞀ,LT ; eY,LY,eV,LV,AT,AꞀ,AY,Ay,λY,λy,a\,b\ +- _`___`_@__ — @_@___`___ # Lꟼ but NOT bꟼ +- _`___@_`__ — __`_`_____ # ⱶƆ,ⱶJ +- _`_@___`__ — __`_`_____ # ⱵƆ,ⱵJ + + +--- Implementation +code: | + val posTable = intArrayOf(7,6,5,4,3,2,1,0,9,8) + + class RuleMask(s: String) { + + private var careBits = 0 + private var ruleBits = 0 + + init { + s.forEachIndexed { index, char -> + when (char) { + '@' -> { + careBits = careBits or (1 shl posTable[index]) + ruleBits = ruleBits or (1 shl posTable[index]) + } + '`' -> { + careBits = careBits or (1 shl posTable[index]) + } + } + } + } + + fun matches(shapeBits: Int) = ((shapeBits and careBits) and ruleBits) == 0 + + } + +--- Pixel 2 +Glyph pos for up/down diacritics +- U: X-position for stack-up diacritics +- D: X-position for stack-down diacritics +- encoding: + - 00000000 100DDDDD 100UUUUU +- If either U/D is unused, centre position will be used. +- Diacritics will be centred on the x-position this parameter points to + +--- Pixel 3 +dot removal for diacritics: +- All 24 bits are used to put replacement character +- encoding: + - RRRRRRRR GGGGGGGG BBBBBBBB + diff --git a/src/net/torvald/terrarumsansbitmap/GlyphProps.kt b/src/net/torvald/terrarumsansbitmap/GlyphProps.kt index 1930dad..60b7f06 100755 --- a/src/net/torvald/terrarumsansbitmap/GlyphProps.kt +++ b/src/net/torvald/terrarumsansbitmap/GlyphProps.kt @@ -10,7 +10,12 @@ data class GlyphProps( val alignXPos: Int, val rtl: Boolean = false, val stackWhere: Int = 0, - var extInfo: IntArray? = null + var extInfo: IntArray? = null, + + val hasKernData: Boolean = false, + val isLowheight: Boolean = false, + val isKernYtype: Boolean = false, + val kerningMask: Int = 255 ) { companion object { const val ALIGN_LEFT = 0 @@ -38,6 +43,21 @@ data class GlyphProps( tags.ushr(8).and(3) ) + constructor(width: Int, tags: Int, isLowheight: Boolean, isKernYtype: Boolean, kerningMask: Int) : this( + width, + tags.ushr(7).and(1) == 1, + tags.ushr(5).and(3), + tags.ushr(1).and(15), + tags.and(1) == 1, + tags.ushr(8).and(3), + null, + + true, + isLowheight, + isKernYtype, + kerningMask + ) + fun isOverlay() = writeOnTop && alignXPos == 1 override fun hashCode(): Int { diff --git a/src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt b/src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt index 522ebd5..6b329fa 100755 --- a/src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt +++ b/src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt @@ -726,13 +726,28 @@ class TerrarumSansBitmap( } } + + // lowheight bit + val isLowHeight = (pixmap.getPixel(codeStartX, codeStartY + 5).and(0xFF) != 0) + + // Keming machine parameters + val kerningBit1 = pixmap.getPixel(codeStartX, codeStartY + 6) + val kerningBit2 = pixmap.getPixel(codeStartX, codeStartY + 7) + val kerningBit3 = pixmap.getPixel(codeStartX, codeStartY + 8) + val isKerningYtype = ((kerningBit1 and 0x80000000.toInt()) != 0) + val kerningMask = kerningBit1.ushr(8).and(0xFFFFFF) + val hasKerningBit = kerningBit1 and 255 != 0//(kerningBit1 and 255 != 0 && kerningMask != 0xFFFF) + + //println("$code: Width $width, tags $tags") + if (hasKerningBit) + println("$code: W $width, tags $tags, low? $isLowHeight, kern ${kerningMask.toString(16).padStart(6,'0')} (raw: ${kerningBit1.toLong().and(4294967295).toString(16).padStart(8,'0')})") /*val isDiacritics = pixmap.getPixel(codeStartX, codeStartY + H - 1).and(0xFF) != 0 if (isDiacritics) glyphWidth = -glyphWidth*/ - glyphProps[code] = GlyphProps(width, tags) + glyphProps[code] = if (hasKerningBit) GlyphProps(width, tags, isLowHeight, isKerningYtype, kerningMask) else GlyphProps(width, tags) // extra info val extCount = glyphProps[code]?.requiredExtInfoCount() ?: 0 @@ -752,6 +767,7 @@ class TerrarumSansBitmap( glyphProps[code]!!.extInfo!![x] = info } } + } } @@ -1337,6 +1353,90 @@ class TerrarumSansBitmap( return crc.value.toInt() } + fun CodePoint.isLowHeight() = glyphProps[this]?.isLowheight == true || this in lowHeightLetters + + private fun getKerning(prevChar: CodePoint, thisChar: CodePoint): Int { + val maskL = glyphProps[prevChar]?.kerningMask + val maskR = glyphProps[thisChar]?.kerningMask + return if (glyphProps[prevChar]?.hasKernData == true && glyphProps[thisChar]?.hasKernData == true) { + val contraction = if (glyphProps[prevChar]?.isKernYtype == true || glyphProps[thisChar]?.isKernYtype == true) -1 else -2 + kerningRules.forEachIndexed { index, it -> + if (it.first.matches(maskL!!) && it.second.matches(maskR!!)) { + println("Kerning rule match #${index+1}: ${prevChar.toChar()}${thisChar.toChar()}, Rule:${it.first} ${it.second}; Contraction: ${-contraction}") + + return contraction + } + } + return 0 + } + else 0 + /*else if (prevChar in lowHeightLetters) { + return if (thisChar in kernTees) kernTee // lh - T + else if (thisChar in kernYees) kernYee // lh - Y + else 0 + } + else if (prevChar in kernElls) { + return if (thisChar in kernTees) kernTee // L - T + else if (thisChar in kernVees) kernYee // L - V + else if (thisChar in kernYees) kernYee // L - Y + else 0 + } + else if (prevChar in kernTees) { + return if (thisChar in lowHeightLetters) kernTee // T - lh + else if (thisChar in kernJays) kernTee // T - J + else if (thisChar in kernAyes) kernYee // T - A + else if (thisChar in kernDees) kernTee // T - d + else 0 + } + else if (prevChar in kernYees) { + return if (thisChar in lowHeightLetters) kernYee // Y - lh + else if (thisChar in kernAyes) kernYee // Y - A + else if (thisChar in kernJays) kernYee // Y - J + else if (thisChar in kernDees) kernYee // Y - d + else 0 + } + else if (prevChar in kernAyes) { + return if (thisChar in kernVees) kernAV // A - V + else if (thisChar in kernTees) kernAV // A - T + else if (thisChar in kernYees) kernYee // A - Y + else 0 + } + else if (prevChar in kernVees) { + return if (thisChar in kernAyes) kernAV // V - A + else if (thisChar in kernJays) kernAV // V - J + else if (thisChar in kernDees) kernAV // V - d + else 0 + } + else if (prevChar in kernGammas) { + return if (thisChar in kernAyes) kernYee // Γ - Α + else if (thisChar in lowHeightLetters) kernTee // Γ - lh + else if (thisChar in kernJays) kernTee // Γ - J + else if (thisChar in kernDees) kernTee // Γ - d + else 0 + } + else if (prevChar in kernBees) { + return if (thisChar in kernTees) kernTee // b - T + else if (thisChar in kernYees) kernYee // b - Y + else 0 + } + else if (prevChar in kernLowVees) { + return if (thisChar in kernTees) kernTee + else if (thisChar in kernLowLambdas) kernAVlow + else 0 + } + else if (prevChar in kernLowLambdas) { + return if (thisChar in kernTees) kernTee + else if (thisChar in kernLowVees) kernAVlow + else 0 + } + else if (prevChar in slashes) { + return if (thisChar in kernDees || thisChar in lowHeightLetters) kernSlash // / - d + else if (thisChar in slashes) kernDoubleSlash + else 0 + } + else 0*/ + } + companion object { private fun Boolean.toSign() = if (this) 1 else -1 @@ -1344,7 +1444,6 @@ class TerrarumSansBitmap( /** * 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 { @@ -1358,7 +1457,6 @@ class TerrarumSansBitmap( } - private val HCF = 0x115F private val HJF = 0x1160 @@ -1813,74 +1911,6 @@ print(','.join(a)) private val kernSlash = -1 private val kernDoubleSlash = -2 - private fun getKerning(prevChar: CodePoint, thisChar: CodePoint): Int { - return if (prevChar in lowHeightLetters) { - return if (thisChar in kernTees) kernTee // lh - T - else if (thisChar in kernYees) kernYee // lh - Y - else 0 - } - else if (prevChar in kernElls) { - return if (thisChar in kernTees) kernTee // L - T - else if (thisChar in kernVees) kernYee // L - V - else if (thisChar in kernYees) kernYee // L - Y - else 0 - } - else if (prevChar in kernTees) { - return if (thisChar in lowHeightLetters) kernTee // T - lh - else if (thisChar in kernJays) kernTee // T - J - else if (thisChar in kernAyes) kernYee // T - A - else if (thisChar in kernDees) kernTee // T - d - else 0 - } - else if (prevChar in kernYees) { - return if (thisChar in lowHeightLetters) kernYee // Y - lh - else if (thisChar in kernAyes) kernYee // Y - A - else if (thisChar in kernJays) kernYee // Y - J - else if (thisChar in kernDees) kernYee // Y - d - else 0 - } - else if (prevChar in kernAyes) { - return if (thisChar in kernVees) kernAV // A - V - else if (thisChar in kernTees) kernAV // A - T - else if (thisChar in kernYees) kernYee // A - Y - else 0 - } - else if (prevChar in kernVees) { - return if (thisChar in kernAyes) kernAV // V - A - else if (thisChar in kernJays) kernAV // V - J - else if (thisChar in kernDees) kernAV // V - d - else 0 - } - else if (prevChar in kernGammas) { - return if (thisChar in kernAyes) kernYee // Γ - Α - else if (thisChar in lowHeightLetters) kernTee // Γ - lh - else if (thisChar in kernJays) kernTee // Γ - J - else if (thisChar in kernDees) kernTee // Γ - d - else 0 - } - else if (prevChar in kernBees) { - return if (thisChar in kernTees) kernTee // b - T - else if (thisChar in kernYees) kernYee // b - Y - else 0 - } - else if (prevChar in kernLowVees) { - return if (thisChar in kernTees) kernTee - else if (thisChar in kernLowLambdas) kernAVlow - else 0 - } - else if (prevChar in kernLowLambdas) { - return if (thisChar in kernTees) kernTee - else if (thisChar in kernLowVees) kernAVlow - else 0 - } - else if (prevChar in slashes) { - return if (thisChar in kernDees || thisChar in lowHeightLetters) kernSlash // / - d - else if (thisChar in slashes) kernDoubleSlash - else 0 - } - else 0 - } - val charsetOverrideDefault = Character.toChars(CHARSET_OVERRIDE_DEFAULT).toSurrogatedString() val charsetOverrideBulgarian = Character.toChars(CHARSET_OVERRIDE_BG_BG).toSurrogatedString() @@ -1890,6 +1920,65 @@ print(','.join(a)) private fun CharArray.toSurrogatedString(): String = "${this[0]}${this[1]}" val noColorCode = toColorCode(0x0000) + + + + // The "Keming" Machine // + + private val kemingBitMask: IntArray = intArrayOf(7,6,5,4,3,2,1,0,15,14).map { 1 shl it }.toIntArray() + + private class RuleMask(s: String) { + + private var careBits = 0 + private var ruleBits = 0 + + init { + s.forEachIndexed { index, char -> + when (char) { + '@' -> { + careBits = careBits or kemingBitMask[index] + ruleBits = ruleBits or kemingBitMask[index] + } + '`' -> { + careBits = careBits or kemingBitMask[index] + } + } + } + } + + fun matches(shapeBits: Int) = ((shapeBits and careBits) == ruleBits) + + override fun toString() = "C:${careBits.toString(2).padStart(16,'0')}-R:${ruleBits.toString(2).padStart(16,'0')}" + } + + /** + * Legend: _ dont care + * @ must have a bit set + * ` must have a bit unset + * Order: ABCDEFGHJK, where + * + * A·B < unset for lowheight miniscules, as in e + * |·| < space we don't care + * C·D < middle hole for majuscules, as in C + * E·F < middle hole for miniscules, as in c + * G·H + *――― < baseline + * |·| + * J·K + */ + private val kerningRules = arrayOf( + RuleMask("_@_`___`__") to RuleMask("`_________"), + RuleMask("_@_@___`__") to RuleMask("`___`_@___"), + RuleMask("___`_`____") to RuleMask("`___@_`___"), + RuleMask("___`_`____") to RuleMask("`_@___`___"), + + RuleMask("_`________") to RuleMask("@_`___`___"), + RuleMask("_`___`_@__") to RuleMask("@_@___`___"), + RuleMask("_`___@_`__") to RuleMask("__`_`_____"), + RuleMask("_`_@___`__") to RuleMask("__`_`_____"), + ) + + // End of the Keming Machine } } diff --git a/src/net/torvald/terrarumtypewriterbitmap/gdx/TerrarumTypewriterBitmap.kt b/src/net/torvald/terrarumtypewriterbitmap/gdx/TerrarumTypewriterBitmap.kt index 9cc3d46..d4a0630 100644 --- a/src/net/torvald/terrarumtypewriterbitmap/gdx/TerrarumTypewriterBitmap.kt +++ b/src/net/torvald/terrarumtypewriterbitmap/gdx/TerrarumTypewriterBitmap.kt @@ -6,14 +6,12 @@ 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.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 diff --git a/work_files/ascii_variable.psd b/work_files/ascii_variable.psd index 3cea387..338a28b 100644 Binary files a/work_files/ascii_variable.psd and b/work_files/ascii_variable.psd differ diff --git a/work_files/latinExtD_variable.psd b/work_files/latinExtD_variable.psd index 9a7946a..22ee750 100644 Binary files a/work_files/latinExtD_variable.psd and b/work_files/latinExtD_variable.psd differ