8 Commits

Author SHA1 Message Date
minjaesong
af334ad20b Enclosed alphanumerics (issue #18) 2026-05-17 17:21:39 +09:00
minjaesong
55ad8ee943 minor fixes 2026-05-06 22:13:27 +09:00
minjaesong
e9fcf6bbce Wenquanyi error fix 2026-04-26 00:38:58 +09:00
minjaesong
45d5b758e3 emoji shiftdown as they should 2026-04-14 23:14:38 +09:00
minjaesong
d3ae868723 emoji wip 2026-03-28 17:36:01 +09:00
minjaesong
372ae9b354 support for Miscellaneous Technical 2026-03-27 21:16:06 +09:00
minjaesong
45027be83c Miscellaneous Technical wip 2026-03-27 01:31:19 +09:00
minjaesong
6bc365fc57 maths ops 2026-03-21 22:15:39 +09:00
23 changed files with 217 additions and 17 deletions

View File

@@ -0,0 +1,30 @@
{
"permissions": {
"allow": [
"WebFetch(domain:github.com)",
"WebSearch",
"Bash(head:*)",
"WebFetch(domain:gitlab.com)",
"Bash(java:*)",
"Bash(ls:*)",
"Bash(jar tf:*)",
"Bash(chmod +x:*)",
"WebFetch(domain:fontforge.org)",
"WebFetch(domain:fonttools.readthedocs.io)",
"Bash(grep:*)",
"Bash(tail:*)",
"Bash(python3:*)",
"Bash(make:*)",
"Bash(git stash:*)",
"Bash(./autokem*)",
"Bash(cmp:*)",
"Bash(wc:*)",
"Bash(pip3:*)",
"Bash(pip install:*)",
"Bash(.venv/bin/python3:*)",
"Bash(.venv/bin/python:*)",
"Bash(find:*)",
"Skill(update-config)"
]
}
}

View File

@@ -0,0 +1,93 @@
---
name: add-unicode-block
description: Add a new Unicode script/block to the Terrarum Sans Bitmap font engine.
---
# Add Unicode Block
## Required inputs
The user must supply:
- **Script name** — human-readable name used in constant/function names (e.g. `Ogham`, `LatinExtE`)
- **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. Extra directions can be given after Unicode range.
## Step 1 — Determine the next sheet index
Read the sheet index constants from both files to find the current highest index (excluding `SHEET_UNKNOWN = 254`):
- Kotlin: `src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt` — grep for `internal const val SHEET_`
- Python: `OTFbuild/sheet_config.py` — grep for `^SHEET_`
The new index = highest existing index + 1.
## Step 2 — Derive identifiers
From the script name, derive:
- **Kotlin constant**: `SHEET_<UPPER_SNAKE>_VARW` (e.g. `SHEET_OGHAM_VARW`)
- **Kotlin indexY function**: `<camelCase>IndexY` (e.g. `oghamIndexY`)
- **Python constant**: same as Kotlin constant
- **Range start hex**: the lower bound codepoint as a `0x`-prefixed Kotlin/Python literal
## Step 3 — Edit both files
Make all 6 edits. Read each section before editing.
### Kotlin: `src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt`
**a) Sheet index constant** — find the block of `internal const val SHEET_*` constants (just before `SHEET_UNKNOWN = 254`) and append:
```kotlin
internal const val SHEET_<NAME>_VARW = <INDEX>
```
**b) fileList entry** — find `internal val fileList` array and append before the closing `)`:
```kotlin
"<tga_filename>",
```
**c) codeRange entry** — find `internal val codeRange` array and append before the closing `)`:
```kotlin
0x<START>..<0x<END>, // SHEET_<NAME>_VARW
```
Use `+` to combine non-contiguous ranges if needed.
**d) getSheetwisePosition when-branch** — find the `when` block that dispatches to indexY functions (just before `else -> ch / 16`) and append:
```kotlin
SHEET_<NAME>_VARW -> <camelCase>IndexY(ch)
```
**e) indexY function** — find the block of private `*IndexY` functions near the bottom of the companion object and append:
```kotlin
private fun <camelCase>IndexY(c: CodePoint) = (c - 0x<START>) / 16
```
### Python: `OTFbuild/sheet_config.py`
**f) Sheet index constant** — find the block of `SHEET_* = <n>` constants (just before `SHEET_UNKNOWN = 254`) and append:
```python
SHEET_<NAME>_VARW = <INDEX>
```
**g) FILE_LIST entry** — find `FILE_LIST = [` array and append before the closing `]`:
```python
"<tga_filename>",
```
**h) CODE_RANGE entry** — find `CODE_RANGE = [` array and append before the closing `]`:
```python
list(range(0x<START>, 0x<END+1>)), # <INDEX>: <ScriptName>
```
**i) index_y lambda** — find the dict in `get_index_y(sheet_index, c)` (just before `SHEET_HANGUL: lambda: 0`) and append:
```python
SHEET_<NAME>_VARW: lambda: (c - 0x<START>) // 16,
```
## Step 4 — Verify
After all edits, confirm:
1. The Kotlin constant, fileList, codeRange, when-branch, and indexY function are all present and consistent.
2. The Python constant, FILE_LIST, CODE_RANGE, and index_y lambda are all present and consistent.
3. The indices in both files match.
4. The range end in `CODE_RANGE` is `end + 1` (Python `range` is exclusive).

View File

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

View File

@@ -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
@@ -81,6 +83,9 @@ SHEET_LATIN_EXTG_VARW = 47
SHEET_OGHAM_VARW = 48
SHEET_COPTIC_VARW = 49
SHEET_CYRILIC_EXTD_VARW = 50
SHEET_MATHS1_VARW = 51
SHEET_EMOJI1 = 52
SHEET_ENCLOSED_ALPHNUM_VARW = 53
SHEET_UNKNOWN = 254
@@ -136,6 +141,9 @@ FILE_LIST = [
"ogham_variable.tga",
"coptic_variable.tga",
"cyrilic_extD_variable.tga",
"maths1_extrawide_variable.tga",
"emoji1.tga",
"enclosed_alphanumeric_variable.tga",
]
CODE_RANGE = [
@@ -190,6 +198,9 @@ CODE_RANGE = [
list(range(0x1680, 0x16A0)), # 48: Ogham
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
list(range(0x2460, 0x2500)), # 53: Enclosed Alphanum
]
CODE_RANGE_HANGUL_COMPAT = range(0x3130, 0x3190)
@@ -271,6 +282,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:
@@ -283,6 +296,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
@@ -575,5 +590,8 @@ def index_y(sheet_index, c):
SHEET_OGHAM_VARW: lambda: (c - 0x1680) // 16,
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_ENCLOSED_ALPHNUM_VARW: lambda: (c - 0x2460) // 16,
SHEET_HANGUL: lambda: 0,
}.get(sheet_index, lambda: c // 16)()

BIN
demo.PNG

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 180 KiB

View File

@@ -132,6 +132,7 @@ How multilingual? Real multilingual!
Cyrillic Supplement
Cyrillic Extended-A/B/C/D
Devanagari
Enclosed Alphanumerics
Enclosed Alphanumeric Supplement
General Punctuations
Georgian􏿆ჼ􀀀
@@ -151,6 +152,8 @@ How multilingual? Real multilingual!
Kana Extended-A
Small Kana Extension
Letterlike Symbols
Mathematical Operators
Miscellaneous Technical
Number Forms
Ogham
Optical Character Recognition

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
src/assets/emoji1.tga LFS Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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
@@ -884,6 +902,9 @@ class TerrarumSansBitmap(
SHEET_OGHAM_VARW -> oghamIndexY(ch)
SHEET_COPTIC_VARW -> copticIndexY(ch)
SHEET_CYRILIC_EXTD_VARW -> cyrilicExtDIndexY(ch)
SHEET_MATHS1_VARW -> maths1IndexY(ch)
SHEET_EMOJI1 -> emoji1IndexY(ch)
SHEET_ENCLOSED_ALPHNUM_VARW -> enclosedAlphnumIndexY(ch)
else -> ch / 16
}
@@ -1010,6 +1031,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) }
@@ -2559,6 +2581,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
@@ -2618,6 +2642,9 @@ class TerrarumSansBitmap(
internal const val SHEET_OGHAM_VARW = 48
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_ENCLOSED_ALPHNUM_VARW = 53
internal const val SHEET_UNKNOWN = 254
@@ -2691,6 +2718,9 @@ class TerrarumSansBitmap(
"ogham_variable.tga",
"coptic_variable.tga",
"cyrilic_extD_variable.tga",
"maths1_extrawide_variable.tga",
"emoji1.tga",
"enclosed_alphanumeric_variable.tga",
)
internal val codeRange = arrayOf( // MUST BE MATCHING WITH SHEET INDICES!!
0..0xFF, // SHEET_ASCII_VARW
@@ -2744,6 +2774,9 @@ class TerrarumSansBitmap(
0x1680..0x169F, // SHEET_OGHAM_VARW
0x2C80..0x2CFF, // SHEET_COPTIC_VARW
0x1E030..0x1E08F, // SHEET_CYRILIC_EXTD_VARW
0x2200..0x23FF, // SHEET_MATHS1_VARW
0x1F600..0x1F64F, // SHEET_EMOJI1
0x2460..0x24FF, // SHEET_ENCLOSED_ALPHNUM_VARW
)
private val codeRangeHangulCompat = 0x3130..0x318F
@@ -3105,6 +3138,9 @@ class TerrarumSansBitmap(
private fun oghamIndexY(c: CodePoint) = (c - 0x1680) / 16
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
private fun enclosedAlphnumIndexY(c: CodePoint) = (c - 0x2460) / 16
val charsetOverrideDefault = Character.toChars(CHARSET_OVERRIDE_DEFAULT).toSurrogatedString()
val charsetOverrideBulgarian = Character.toChars(CHARSET_OVERRIDE_BG_BG).toSurrogatedString()

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
work_files/emoji1.kra LFS Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.