diff --git a/.idea/workspace.xml b/.idea/workspace.xml index d160b9b..3a2729c 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -7,15 +7,18 @@ - - + + + + + @@ -514,18 +517,18 @@ - + + - + - @@ -839,57 +842,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -914,11 +866,32 @@ + + + + + + + + + + + + + + + + + + + + + - - + + @@ -926,10 +899,40 @@ - + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dd33fdf..ff1b8ce 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,7 +48,7 @@ Green-tinted area (should be 10 px tall) contains the tags. Tags are defined as 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 - 0 == Write on top of prev chars (e.g. diacritics) + 1 == write-on-top, usually it's diatritics but some are not (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) ``` diff --git a/FontTestGDX/lib/TerrarumSansBitmap.jar b/FontTestGDX/lib/TerrarumSansBitmap.jar index c15e6d4..d8a30a6 100644 Binary files a/FontTestGDX/lib/TerrarumSansBitmap.jar and b/FontTestGDX/lib/TerrarumSansBitmap.jar differ diff --git a/FontTestGDX/src/FontTestGDX.kt b/FontTestGDX/src/FontTestGDX.kt index 3027ace..c055cdf 100644 --- a/FontTestGDX/src/FontTestGDX.kt +++ b/FontTestGDX/src/FontTestGDX.kt @@ -23,8 +23,8 @@ class FontTestGDX : Game() { lateinit var camera: OrthographicCamera - private val demotextName = "testtext.txt" - private val outimageName = "testing.png" + private val demotextName = "demotext.txt" + private val outimageName = "demo.png" override fun create() { font = GameFontBase("./assets", flipY = false, errorOnUnknownChar = true) // must test for two flipY cases @@ -256,7 +256,7 @@ class FontTestGDX : Game() { lateinit var appConfig: LwjglApplicationConfiguration const val TEXW = 874 -const val TEXH = 2060 +const val TEXH = 2400 fun main(args: Array) { appConfig = LwjglApplicationConfiguration() diff --git a/assets/ascii_variable.tga b/assets/ascii_variable.tga index 84563e3..24a6bff 100644 Binary files a/assets/ascii_variable.tga and b/assets/ascii_variable.tga differ diff --git a/assets/devanagari_bengali_variable.tga b/assets/devanagari_bengali_variable.tga index 5ec8689..9c993d5 100644 Binary files a/assets/devanagari_bengali_variable.tga and b/assets/devanagari_bengali_variable.tga differ diff --git a/assets/unipunct_variable.tga b/assets/unipunct_variable.tga index 9be4fd9..c3ab5f8 100644 Binary files a/assets/unipunct_variable.tga and b/assets/unipunct_variable.tga differ diff --git a/demo.PNG b/demo.PNG index 0164c90..a9b715a 100644 Binary files a/demo.PNG and b/demo.PNG differ diff --git a/demotext.txt b/demotext.txt index 37bdffc..f2c8ad6 100644 --- a/demotext.txt +++ b/demotext.txt @@ -26,6 +26,7 @@ How multilingual? Real multilingual! 􏻬գրիչս վայր դրի, վեր կացա և պատրաստվում էի, որ քնեմ, երբ հանկարծ դռանս զանգակը հնչեց􀀀 􏻬ՄՇԱԿԻՉ ԿԱՄ ԿԵՆՏՐՈՆԱԿԱՆ ՄՇԱԿԻՉ ՀԱՆԳՈՒՅՑԸ ՀԱՆԴԻՍԱՆՈՒՄ Է ՀԱՄԱԿԱՐԳՉԻ ՍԱՐՔԱՎՈՐՈՒՄՆԵՐԻՑ􀀀 􏻬Zəfər, jaketini də papağını da götür, bu axşam hava çox soyuq olacaq􀀀 + 􏻬আমি কাঁচ খেতে পারি, তাতে আমার কোনো ক্ষতি হয় না। 􀀀 􏻬󿿹Под южно дърво, цъфтящо в синьо, бягаше малко пухкаво зайче󿿸􀀀 􏻬ᎠᏍᎦᏯᎡᎦᎢᎾᎨᎢᎣᏍᏓᎤᎩᏍᏗᎥᎴᏓᎯᎲᎢᏔᎵᏕᎦᏟᏗᏖᎸᎳᏗᏗᎧᎵᎢᏘᎴᎩ ᏙᏱᏗᏜᏫᏗᏣᏚᎦᏫᏛᏄᏓᎦᏝᏃᎠᎾᏗᎭᏞᎦᎯᎦᏘᏓᏠᎨᏏᏕᏡᎬᏢᏓᏥᏩᏝᎡᎢᎪᎢ􀀀 􏻬ᎠᎦᏂᏗᎮᎢᎫᎩᎬᏩᎴᎢᎠᏆᏅᏛᎫᏊᎾᎥᎠᏁᏙᎲᏐᏈᎵᎤᎩᎸᏓᏭᎷᏤᎢᏏᏉᏯᏌᏊ ᎤᏂᏋᎢᏡᎬᎢᎰᏩᎬᏤᎵᏍᏗᏱᎩᎱᎱᎤᎩᎴᎢᏦᎢᎠᏂᏧᏣᏨᎦᏥᎪᎥᏌᏊᎤᎶᏒᎢᎢᏡᎬᎢ􀀀 @@ -93,7 +94,7 @@ How multilingual? Real multilingual! 􏃯Unicode References:􀀀 Basic Latin Latin-1 Latin Extension A Latin Extionsion B IPA Extension Greek Cyrillic - Cyrillic Supplement Armenian Devanagari Thai Georgian Runic Cherokee Georgian Extended + Cyrillic Supplement Armenian Devanagari Bengali Thai Georgian Runic Cherokee Georgian Extended General Punctuations CJK Symbols Kana Kana Phonetic Extension CJK Unihan Extension A CJK Unihan Hangul Syllables Fullwidth Forms Kana Supplement diff --git a/src/net/torvald/terrarumsansbitmap/GlyphProps.kt b/src/net/torvald/terrarumsansbitmap/GlyphProps.kt index b3ccd96..1930dad 100644 --- a/src/net/torvald/terrarumsansbitmap/GlyphProps.kt +++ b/src/net/torvald/terrarumsansbitmap/GlyphProps.kt @@ -9,7 +9,8 @@ data class GlyphProps( val alignWhere: Int, val alignXPos: Int, val rtl: Boolean = false, - val stackWhere: Int = 0 + val stackWhere: Int = 0, + var extInfo: IntArray? = null ) { companion object { const val ALIGN_LEFT = 0 @@ -24,6 +25,8 @@ data class GlyphProps( const val DIA_OVERLAY = 1 const val DIA_JOINER = 2 + + private fun Boolean.toInt() = if (this) 1 else 0 } constructor(width: Int, tags: Int) : this( @@ -36,4 +39,28 @@ data class GlyphProps( ) fun isOverlay() = writeOnTop && alignXPos == 1 + + override fun hashCode(): Int { + val tags = rtl.toInt() or alignXPos.shl(1) or alignWhere.shl(5) or + writeOnTop.toInt().shl(7) or stackWhere.shl(8) + + var hash = -2128831034 + + extInfo?.forEach { + hash = hash xor it + hash = hash * 16777619 + } + + hash = hash xor tags + hash = hash * 167677619 + + return hash + } + + override fun equals(other: Any?): Boolean { + // comparing hash because I'm lazy + return other is GlyphProps && this.hashCode() == other.hashCode() + } + + fun requiredExtInfoCount() = if (stackWhere == STACK_BEFORE_N_AFTER) 2 else 0 } \ No newline at end of file diff --git a/src/net/torvald/terrarumsansbitmap/gdx/GameFontBase.kt b/src/net/torvald/terrarumsansbitmap/gdx/GameFontBase.kt index 55cfc26..be50b2d 100644 --- a/src/net/torvald/terrarumsansbitmap/gdx/GameFontBase.kt +++ b/src/net/torvald/terrarumsansbitmap/gdx/GameFontBase.kt @@ -31,7 +31,6 @@ import com.badlogic.gdx.graphics.Texture import com.badlogic.gdx.graphics.g2d.* import net.torvald.terrarumsansbitmap.GlyphProps import java.io.BufferedOutputStream -import java.io.File import java.io.FileOutputStream import java.util.zip.GZIPInputStream @@ -522,7 +521,9 @@ class GameFontBase(fontDir: String, val noShadow: Boolean = false, val flipY: Bo posXbuffer[nonDiacriticCounter] + W_VAR_INIT + alignmentOffset + interchar else posXbuffer[nonDiacriticCounter] + itsProp.width + alignmentOffset + interchar + nonDiacriticCounter = charIndex + stackUpwardCounter = 0 stackDownwardCounter = 0 } @@ -536,46 +537,53 @@ class GameFontBase(fontDir: String, val noShadow: Boolean = false, val flipY: Bo else { // set X pos according to alignment information posXbuffer[charIndex] = when (thisProp.alignWhere) { - GlyphProps.ALIGN_LEFT -> posXbuffer[nonDiacriticCounter] + GlyphProps.ALIGN_LEFT, GlyphProps.ALIGN_BEFORE -> posXbuffer[nonDiacriticCounter] GlyphProps.ALIGN_RIGHT -> { posXbuffer[nonDiacriticCounter] - (W_VAR_INIT - itsProp.width) } GlyphProps.ALIGN_CENTRE -> { val alignXPos = if (itsProp.alignXPos == 0) itsProp.width.div(2) else itsProp.alignXPos - posXbuffer[nonDiacriticCounter] + alignXPos - (W_VAR_INIT - 1).div(2) + if (itsProp.alignWhere == GlyphProps.ALIGN_RIGHT) { + posXbuffer[nonDiacriticCounter] + alignXPos + (itsProp.width + 1).div(2) + } + else { + posXbuffer[nonDiacriticCounter] + alignXPos - (W_VAR_INIT - 1).div(2) + } } else -> throw InternalError("Unsupported alignment: ${thisProp.alignWhere}") } // set Y pos according to diacritics position - when (thisProp.stackWhere) { - GlyphProps.STACK_DOWN -> { - posYbuffer[charIndex] = H_DIACRITICS * stackDownwardCounter - stackDownwardCounter++ - } - GlyphProps.STACK_UP -> { - posYbuffer[charIndex] = -H_DIACRITICS * stackUpwardCounter - - // shift down on lowercase if applicable - if (getSheetType(thisChar) in autoShiftDownOnLowercase && - lastNonDiacriticChar.isLowHeight()) { - if (thisProp.alignXPos == GlyphProps.DIA_OVERLAY) - posYbuffer[charIndex] += H_OVERLAY_LOWERCASE_SHIFTDOWN - else - posYbuffer[charIndex] += H_STACKUP_LOWERCASE_SHIFTDOWN + if (thisProp.alignWhere == GlyphProps.ALIGN_CENTRE) { + when (thisProp.stackWhere) { + GlyphProps.STACK_DOWN -> { + posYbuffer[charIndex] = H_DIACRITICS * stackDownwardCounter + stackDownwardCounter++ } + GlyphProps.STACK_UP -> { + posYbuffer[charIndex] = -H_DIACRITICS * stackUpwardCounter - stackUpwardCounter++ - } - GlyphProps.STACK_UP_N_DOWN -> { - posYbuffer[charIndex] = H_DIACRITICS * stackDownwardCounter - stackDownwardCounter++ - posYbuffer[charIndex] = -H_DIACRITICS * stackUpwardCounter - stackUpwardCounter++ - } + // shift down on lowercase if applicable + if (getSheetType(thisChar) in autoShiftDownOnLowercase && + lastNonDiacriticChar.isLowHeight()) { + if (thisProp.alignXPos == GlyphProps.DIA_OVERLAY) + posYbuffer[charIndex] += H_OVERLAY_LOWERCASE_SHIFTDOWN + else + posYbuffer[charIndex] += H_STACKUP_LOWERCASE_SHIFTDOWN + } + + stackUpwardCounter++ + } + GlyphProps.STACK_UP_N_DOWN -> { + posYbuffer[charIndex] = H_DIACRITICS * stackDownwardCounter + stackDownwardCounter++ + posYbuffer[charIndex] = -H_DIACRITICS * stackUpwardCounter + stackUpwardCounter++ + } // for BEFORE_N_AFTER, do nothing in here + } } } } @@ -972,6 +980,25 @@ class GameFontBase(fontDir: String, val noShadow: Boolean = false, val flipY: Bo 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 + } + } } } @@ -998,7 +1025,9 @@ class GameFontBase(fontDir: String, val noShadow: Boolean = false, val flipY: Bo glyphProps[0x1D79] = GlyphProps(9, 0) - glyphProps[0xFFFD] = nullProp + // U+007F is DEL originally, but this font stores bitmap of Replacement Character (U+FFFD) + // to this position. String replacer will replace U+FFFD into U+007F. + glyphProps[0x7F] = GlyphProps(15, 0) } private val glyphLayout = GlyphLayout() @@ -1021,15 +1050,34 @@ class GameFontBase(fontDir: String, val noShadow: Boolean = false, val flipY: Bo if (i < this.lastIndex && c.isHighSurrogate()) { val cNext = this[i + 1] - if (!cNext.isLowSurrogate()) - throw IllegalArgumentException("Malformed UTF-16 String: High surrogate must be paired with low surrogate") + if (!cNext.isLowSurrogate()) { + // replace with Unicode replacement char + seq.add(0xFFFD) + } + else { + val H = c + val L = cNext - val H = c - val L = cNext + seq.add(Character.toCodePoint(H, L)) - seq.add(Character.toCodePoint(H, L)) - - i++ // skip next char (guaranteed to be Low Surrogate) + i++ // skip next char (guaranteed to be Low Surrogate) + } + } + // rearrange {letter, before-and-after diacritics} as {letter, before-diacritics, after-diacritics} + // {letter, before-diacritics} part will be dealt with swapping code below + // DOES NOT WORK if said diacritics has codepoint > 0xFFFF + else if (i < this.lastIndex && this[i + 1].toInt() <= 0xFFFF && + glyphProps[this[i + 1].toInt()]?.stackWhere == GlyphProps.STACK_BEFORE_N_AFTER) { + val diacriticsProp = glyphProps[this[i + 1].toInt()]!! + seq.add(c.toInt()) + seq.add(diacriticsProp.extInfo!![0]) + seq.add(diacriticsProp.extInfo!![1]) + i++ + } + // U+007F is DEL originally, but this font stores bitmap of Replacement Character (U+FFFD) + // to this position. This line will replace U+FFFD into U+007F. + else if (c == '�') { + seq.add(0x7F) } else { seq.add(c.toInt()) @@ -1038,10 +1086,10 @@ class GameFontBase(fontDir: String, val noShadow: Boolean = false, val flipY: Bo i++ } - // swap position of {letter, diacritics that comes before the letter} i = 1 while (i <= seq.lastIndex) { + if ((glyphProps[seq[i]] ?: nullProp).alignWhere == GlyphProps.ALIGN_BEFORE) { val t = seq[i - 1] seq[i - 1] = seq[i] @@ -1051,7 +1099,6 @@ class GameFontBase(fontDir: String, val noShadow: Boolean = false, val flipY: Bo i++ } - return seq } diff --git a/testing.PNG b/testing.PNG index 18a33b7..841c0c0 100644 Binary files a/testing.PNG and b/testing.PNG differ diff --git a/testtext.txt b/testtext.txt index a080c9e..a6d3739 100644 --- a/testtext.txt +++ b/testtext.txt @@ -1,22 +1,5 @@ -O̸ -o̸ - -Received Pronunciation IPA: /ˌɪntəˈnæʃənəl/, [ˌɪntəˈnæʃənəɫ] -General American IPA: /ˌɪntɚˈnæʃənəl/, [ˌɪntɚˈnæʃənəɫ], [ˌɪɾ̃ɚˈnæʃənəɫ] -Rhymes: -ɛntəl (wtf wiktionary ??) - -ˈkʰomɐ gɛts ɐ ˈkʰjuɚ wɛl çiəz ə ˈstʌɹi fɔ ˈju ˈsɐɾə ˈpɛɾi wɔz ə bɛtʰəˈna˞li ˈnʌɚs hu hæd bin ˈwʌ˞kɪŋ deɪli æt æn -ˈɔʊl̴də d͡zʉ in ə dɪˈzʌɚtɪdə dɪsˈtɹʷɪkt ɔv zə tʰ ˈtʰɛɹɪtəɹi soʊ ʃi wʌz ˈvɛɹi ˈhæpi s tʉ stat ə njʉ d͡ʒɔb æt ə -ˈsʌbʌb˺ ˈpɹaɪbɛt pɹaktis in noʊsə ˈskweɚ niə zə ˈdjʉk ˈstoʊi ˈtaʊɚ ðæt ˈeɾiə wəz mʌt͡ʃ ˈniɾə fɔ hɐ ænd mɔə -tʉ laɪk˺ hɐ ˈlaɪkiŋgə ˈibn̩ so ɔ̃ ha fa˞st ˈmɔnɪŋ ʃi fɛlt͡s s t͡stɹɛst ʃi eɪt ə bɔl̴ ɔb˺ ˈpɔɹʷɪd͡ʒ t͡ʃɛkt hɐ˞sɛlf ɪn ðə -ˈmiɹəɚ ænd wɑʃt hɐ˞ ɸeɪs ɪn ə ˈhʌ˞li zɛn ʃi pʊt ɑn ə pɹeɪn bə ˈjɛloʊ dɹɛs ænd ə ɸʊlɪɸʊlis ˈd͡ʒækɛt pikt ap -hɐ˞ kʰit ænd ˈhɛdɪdə fɔ ˈwʌ˞kʰ - -ˈkʊmɐ gɛts ə ˈkjɚ wɛl̴ ˈɸ͜hi˞ɹɪzə ˈstʌɾi fɔ ʔəju saɹəʔ ˈpɛɹi wɑzə bə ˈbɛtənəɹi bɛt˺ ˈbɛtəˌɹɪnəɹi ənʌs f f hu havə bin -ˈw̰ʌ˞kɪŋ deɹi ʔat˺ ʔan ʔat an ol̴d͜zʉ in zə diˈzɑɹɛd d ˈdistɹɪkt ɔb ðəʔ ˈtɛɹɪtəɹʷi soʊ çi wʌz ˈbɛɹɪ ˈhɑpi tʉ stɑ˞t˺ -njʉ ə ˈnjʉ d͡ʒoʊb̥ ɐʔ to s̩ˈpɚb pɹaɪvɛt ˈpɹaktɪs ɪn nɔsˈkwɛɚ niə ðə ˈdʌk sɹit˺ t ˈtɔʌ˞ zat˺ ˈeɹiə wɑz mat͡ʃ ˈniɹɪ ˈniɹə fɔ -hʌ̥ɕʉ̥ fɔ hɚ ændə mɔ̰ mɔə tu ˈħə ˈɹaɪkɪŋ ˈivn̩ zoʊ ɔn ɸ hɐ fɐst mɔɹɪŋ ʃi fɛlt ˈsɹɛɾɛ ɛ̰ ʃi eɪt˺ ʌ boʊl̴ ɔb ˈpɹis æ̃ ˈt͡ʃɛkʰt -ˈhɑsɛlf ɪnə ˈmiɹə and wɔʃt hɐ fʷeɪs ɪn ˈhʌ˞ɹi ʌ ˈðɛn si pʊt ɔn ðə pɹeɪnə ˈjɛɹoʊ ˈdɹɛsɪz an fɹis ˈd͡ʒækɛt n̩ pik ʌpt -ˈhɑ kɪt an ˈhɛdɪd fɔ ə fɔ wɔk fɔ ˈwʌ˞kʰ æ̰̃ - -acegijmnopqrsuvwxyzɱɳʙɾɽʒʂʐʋɹɻɥɟɡɢʛȵɲŋɴʀɕʑçʝxɣχʁʜʍɰʟɨʉɯuʊøɘɵɤəɛœɜɞʌɔæɐɶɑɒɚɝɩɪʅʈʏʞ \ No newline at end of file +কঁা (incorrect order, ??? rendering) +কাঁ (correct order, ??? rendering) +हैहैहै +সিওল কোরিয়া রাজধানী +सियोल कोरिया की राजधानी है \ No newline at end of file