First working version of the Keming Machine

This commit is contained in:
minjaesong
2021-11-20 15:46:43 +09:00
parent 12fc3eee03
commit 33ae3fa48a
13 changed files with 319 additions and 86 deletions

View File

@@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 KiB

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 KiB

After

Width:  |  Height:  |  Size: 280 KiB

BIN
demo.PNG

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 142 KiB

View File

@@ -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, its ugly.”

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

115
keming_machine.txt Normal file
View File

@@ -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:
- <MSB> Y0000000 JK000000 ABCDEFGH <LSB>
- 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:
- <MSB> 00000000 100DDDDD 100UUUUU <LSB>
- 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:
- <MSB> RRRRRRRR GGGGGGGG BBBBBBBB <LSB>

View File

@@ -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 {

View File

@@ -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<TextCacheObj> {
@@ -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
}
}

View File

@@ -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

Binary file not shown.

Binary file not shown.