From 45d5b758e380713691f8e243d5bbdc4ee23578fb Mon Sep 17 00:00:00 2001 From: minjaesong Date: Tue, 14 Apr 2026 23:14:38 +0900 Subject: [PATCH] emoji shiftdown as they should --- .claude/skills/add-unicode-block/SKILL.md | 2 +- OTFbuild/font_builder.py | 5 ++++ OTFbuild/sheet_config.py | 10 +++++++ src/assets/emoji1.tga | 3 +++ .../gdx/TerrarumSansBitmap.kt | 26 +++++++++++++++++++ 5 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 src/assets/emoji1.tga diff --git a/.claude/skills/add-unicode-block/SKILL.md b/.claude/skills/add-unicode-block/SKILL.md index 3917a5c..17f11ff 100644 --- a/.claude/skills/add-unicode-block/SKILL.md +++ b/.claude/skills/add-unicode-block/SKILL.md @@ -11,7 +11,7 @@ The user must supply: - **TGA filename** — the sprite sheet filename without path (e.g. `ogham_variable.tga`) - **Unicode range** — start and end codepoints inclusive (e.g. `U+1680..U+169F`) -If any of these are missing, ask for them before proceeding. +If any of these are missing, ask for them before proceeding. Extra directions can be given after Unicode range. ## Step 1 — Determine the next sheet index diff --git a/OTFbuild/font_builder.py b/OTFbuild/font_builder.py index 36b9e09..58d036d 100644 --- a/OTFbuild/font_builder.py +++ b/OTFbuild/font_builder.py @@ -332,6 +332,7 @@ def build_font(assets_dir, output_path, no_bitmap=False, no_features=False): charstrings[".notdef"] = pen.getCharString() _unihan_cps = set(SC.CODE_RANGE[SC.SHEET_UNIHAN]) + _emoji1_cps = set(SC.CODE_RANGE[SC.SHEET_EMOJI1]) _base_offsets = {} # glyph_name -> (x_offset, y_offset) for COLR layers traced_count = 0 @@ -382,6 +383,10 @@ def build_font(assets_dir, output_path, no_bitmap=False, no_features=False): if cp in _unihan_cps: y_offset -= ((SC.H - SC.H_UNIHAN) // 2) * SCALE + # Emoji1 glyphs are 16px tall in a 20px cell; same 2px top/bottom padding. + if cp in _emoji1_cps: + y_offset -= ((SC.H - SC.H_EMOJI1) // 2) * SCALE + # Hangul jungseong/jongseong PUA variants (rows 15-18) have zero # advance and overlay the preceding choseong. Shift their outlines # left by one syllable cell width so they render at the same position. diff --git a/OTFbuild/sheet_config.py b/OTFbuild/sheet_config.py index 9652266..1bbff4b 100644 --- a/OTFbuild/sheet_config.py +++ b/OTFbuild/sheet_config.py @@ -6,8 +6,10 @@ Ported from TerrarumSansBitmap.kt companion object and SheetConfig.kt. # Font metrics H = 20 H_UNIHAN = 16 +H_EMOJI1 = 16 W_HANGUL_BASE = 13 W_UNIHAN = 16 +W_EMOJI1 = 17 W_LATIN_WIDE = 9 W_VAR_INIT = 15 W_WIDEVAR_INIT = 31 @@ -82,6 +84,7 @@ SHEET_OGHAM_VARW = 48 SHEET_COPTIC_VARW = 49 SHEET_CYRILIC_EXTD_VARW = 50 SHEET_MATHS1_VARW = 51 +SHEET_EMOJI1 = 52 SHEET_UNKNOWN = 254 @@ -138,6 +141,7 @@ FILE_LIST = [ "coptic_variable.tga", "cyrilic_extD_variable.tga", "maths1_extrawide_variable.tga", + "emoji1.tga", ] CODE_RANGE = [ @@ -193,6 +197,7 @@ CODE_RANGE = [ list(range(0x2C80, 0x2D00)), # 49: Coptic list(range(0x1E030, 0x1E090)), # 50: Cyrillic Ext D list(range(0x2200, 0x2400)), # 51: Maths1 + list(range(0x1F600, 0x1F650)), # 52: Emoji1 ] CODE_RANGE_HANGUL_COMPAT = range(0x3130, 0x3190) @@ -274,6 +279,8 @@ def get_cell_width(sheet_index): return W_VAR_INIT + HGAP_VAR # 16 if sheet_index == SHEET_UNIHAN: return W_UNIHAN + if sheet_index == SHEET_EMOJI1: + return W_EMOJI1 if sheet_index == SHEET_HANGUL: return W_HANGUL_BASE if sheet_index == SHEET_CUSTOM_SYM: @@ -286,6 +293,8 @@ def get_cell_width(sheet_index): def get_cell_height(sheet_index): if sheet_index == SHEET_UNIHAN: return H_UNIHAN + if sheet_index == SHEET_EMOJI1: + return H_EMOJI1 if sheet_index == SHEET_CUSTOM_SYM: return SIZE_CUSTOM_SYM return H @@ -579,5 +588,6 @@ def index_y(sheet_index, c): SHEET_COPTIC_VARW: lambda: (c - 0x2C80) // 16, SHEET_CYRILIC_EXTD_VARW: lambda: (c - 0x1E030) // 16, SHEET_MATHS1_VARW: lambda: (c - 0x2200) // 16, + SHEET_EMOJI1: lambda: (c - 0x1F600) // 16, SHEET_HANGUL: lambda: 0, }.get(sheet_index, lambda: c // 16)() diff --git a/src/assets/emoji1.tga b/src/assets/emoji1.tga new file mode 100644 index 0000000..fda6e12 --- /dev/null +++ b/src/assets/emoji1.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cba62eddfc17af210dbb1a279376d22c42266e377a2979642886f0e92288e572 +size 87058 diff --git a/src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt b/src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt index c40e7f1..d5d690c 100755 --- a/src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt +++ b/src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt @@ -328,6 +328,7 @@ class TerrarumSansBitmap( init { atlas = GlyphAtlas(4096, 4096) var unihanPixmap: Pixmap? = null + var emoji1Pixmap: Pixmap? = null // first we create pixmap to read pixels, then pack into atlas fileList.forEachIndexed { index, it -> @@ -373,6 +374,10 @@ class TerrarumSansBitmap( // defer wenquanyi packing to after all other sheets unihanPixmap = pixmap } + else if (index == SHEET_EMOJI1) { + // defer emoji1 packing to after all other sheets + emoji1Pixmap = pixmap + } else { val texRegPack = if (isExtraWide) PixmapRegionPack(pixmap, W_WIDEVAR_INIT, H, HGAP_VAR, 0, xySwapped = isXYSwapped) @@ -418,6 +423,14 @@ class TerrarumSansBitmap( it.dispose() } + // pack emoji1 as a contiguous blit (fixed 17x16 cells, 2px top/bottom padding) + emoji1Pixmap?.let { + val cols = it.width / W_EMOJI1 + val rows = it.height / H_EMOJI1 + atlas.blitSheet(SHEET_EMOJI1, it, W_EMOJI1, H_EMOJI1, cols, rows) + it.dispose() + } + // make sure null char is actually null (draws nothing and has zero width) atlas.getRegion(SHEET_ASCII_VARW, 0, 0)?.let { atlas.clearRegion(it) } glyphProps[0] = GlyphProps(0) @@ -446,6 +459,7 @@ class TerrarumSansBitmap( } private val offsetUnihan = (H - H_UNIHAN) / 2 + private val offsetEmoji1 = (H - H_EMOJI1) / 2 private val offsetCustomSym = (H - SIZE_CUSTOM_SYM) / 2 private var flagFirstRun = true @@ -617,6 +631,8 @@ class TerrarumSansBitmap( val posY = posmap.y[index].flipY() + if (sheetID == SHEET_UNIHAN) // evil exceptions offsetUnihan + else if (sheetID == SHEET_EMOJI1) + offsetEmoji1 else if (sheetID == SHEET_CUSTOM_SYM) offsetCustomSym else 0 @@ -728,6 +744,8 @@ class TerrarumSansBitmap( val posY = posmap.y[index].flipY() + if (sheetID == SHEET_UNIHAN) // evil exceptions offsetUnihan + else if (sheetID == SHEET_EMOJI1) + offsetEmoji1 else if (sheetID == SHEET_CUSTOM_SYM) offsetCustomSym else 0 @@ -885,6 +903,7 @@ class TerrarumSansBitmap( SHEET_COPTIC_VARW -> copticIndexY(ch) SHEET_CYRILIC_EXTD_VARW -> cyrilicExtDIndexY(ch) SHEET_MATHS1_VARW -> maths1IndexY(ch) + SHEET_EMOJI1 -> emoji1IndexY(ch) else -> ch / 16 } @@ -1011,6 +1030,7 @@ class TerrarumSansBitmap( codeRangeHangulCompat.forEach { glyphProps[it] = GlyphProps(W_HANGUL_BASE) } codeRange[SHEET_RUNIC].forEach { glyphProps[it] = GlyphProps(9) } codeRange[SHEET_UNIHAN].forEach { glyphProps[it] = GlyphProps(W_UNIHAN) } + codeRange[SHEET_EMOJI1].forEach { glyphProps[it] = GlyphProps(W_EMOJI1) } (0xD800..0xDFFF).forEach { glyphProps[it] = GlyphProps(0) } (0x100000..0x10FFFF).forEach { glyphProps[it] = GlyphProps(0) } (0xFFFA0..0xFFFFF).forEach { glyphProps[it] = GlyphProps(0) } @@ -2560,6 +2580,8 @@ class TerrarumSansBitmap( internal const val H = 20 internal const val H_UNIHAN = 16 + internal const val W_EMOJI1 = 17 + internal const val H_EMOJI1 = 16 internal const val H_DIACRITICS = 3 @@ -2620,6 +2642,7 @@ class TerrarumSansBitmap( internal const val SHEET_COPTIC_VARW = 49 internal const val SHEET_CYRILIC_EXTD_VARW = 50 internal const val SHEET_MATHS1_VARW = 51 + internal const val SHEET_EMOJI1 = 52 internal const val SHEET_UNKNOWN = 254 @@ -2694,6 +2717,7 @@ class TerrarumSansBitmap( "coptic_variable.tga", "cyrilic_extD_variable.tga", "maths1_extrawide_variable.tga", + "emoji1.tga", ) internal val codeRange = arrayOf( // MUST BE MATCHING WITH SHEET INDICES!! 0..0xFF, // SHEET_ASCII_VARW @@ -2748,6 +2772,7 @@ class TerrarumSansBitmap( 0x2C80..0x2CFF, // SHEET_COPTIC_VARW 0x1E030..0x1E08F, // SHEET_CYRILIC_EXTD_VARW 0x2200..0x23FF, // SHEET_MATHS1_VARW + 0x1F600..0x1F64F, // SHEET_EMOJI1 ) private val codeRangeHangulCompat = 0x3130..0x318F @@ -3110,6 +3135,7 @@ class TerrarumSansBitmap( private fun copticIndexY(c: CodePoint) = (c - 0x2C80) / 16 private fun cyrilicExtDIndexY(c: CodePoint) = (c - 0x1E030) / 16 private fun maths1IndexY(c: CodePoint) = (c - 0x2200) / 16 + private fun emoji1IndexY(c: CodePoint) = (c - 0x1F600) / 16 val charsetOverrideDefault = Character.toChars(CHARSET_OVERRIDE_DEFAULT).toSurrogatedString() val charsetOverrideBulgarian = Character.toChars(CHARSET_OVERRIDE_BG_BG).toSurrogatedString()