2 Commits

Author SHA1 Message Date
minjaesong
76f223aee8 ogham 2026-03-14 19:32:15 +09:00
minjaesong
662dc5b093 glyph texture atlas (2) 2026-03-14 16:06:31 +09:00
7 changed files with 109 additions and 14 deletions

View File

@@ -78,6 +78,7 @@ SHEET_CYRILIC_EXTC_VARW = 44
SHEET_LATIN_EXTE_VARW = 45
SHEET_LATIN_EXTF_VARW = 46
SHEET_LATIN_EXTG_VARW = 47
SHEET_OGHAM_VARW = 48
SHEET_UNKNOWN = 254
@@ -130,6 +131,7 @@ FILE_LIST = [
"latinExtE_variable.tga",
"latinExtF_variable.tga",
"latinExtG_variable.tga",
"ogham_variable.tga",
]
CODE_RANGE = [
@@ -181,6 +183,7 @@ CODE_RANGE = [
list(range(0xAB30, 0xAB70)), # 45: Latin Ext E
list(range(0x10780, 0x107C0)), # 46: Latin Ext F
list(range(0x1DF00, 0x1E000)), # 47: Latin Ext G
list(range(0x1680, 0x16A0)), # 48: Ogham
]
CODE_RANGE_HANGUL_COMPAT = range(0x3130, 0x3190)
@@ -563,5 +566,6 @@ def index_y(sheet_index, c):
SHEET_LATIN_EXTE_VARW: lambda: (c - 0xAB30) // 16,
SHEET_LATIN_EXTF_VARW: lambda: (c - 0x10780) // 16,
SHEET_LATIN_EXTG_VARW: lambda: (c - 0x1DF00) // 16,
SHEET_OGHAM_VARW: lambda: (c - 0x1680) // 16,
SHEET_HANGUL: lambda: 0,
}.get(sheet_index, lambda: c // 16)()

BIN
demo.PNG

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

After

Width:  |  Height:  |  Size: 177 KiB

View File

@@ -147,9 +147,10 @@ How multilingual? Real multilingual!
Katakana Phonetic Extensions
Kana Supplement
Kana Extended-A
Number Forms
Small Kana Extension
Letterlike Symbols
Number Forms
Ogham
Phonetic Extensions
Phonetic Extensions Supplement
Runic

BIN
src/assets/ogham_variable.tga LFS Normal file

Binary file not shown.

View File

@@ -13,7 +13,7 @@ data class AtlasRegion(
class GlyphAtlas(val atlasWidth: Int, val atlasHeight: Int) {
val pixmap = Pixmap(atlasWidth, atlasHeight, Pixmap.Format.RGBA8888).also { it.blending = Pixmap.Blending.None }
val pixmap = Pixmap(atlasWidth, atlasHeight, Pixmap.Format.RGBA8888).also { it.blending = Pixmap.Blending.SourceOver }
private val regions = HashMap<Long, AtlasRegion>()
@@ -21,25 +21,78 @@ class GlyphAtlas(val atlasWidth: Int, val atlasHeight: Int) {
private var cursorY = 0
private var shelfHeight = 0
private val pendingCells = ArrayList<PendingCell>()
private class PendingCell(
val sheetID: Int,
val cellX: Int,
val cellY: Int,
val cropped: Pixmap,
val offsetX: Int,
val offsetY: Int
)
private fun atlasKey(sheetID: Int, cellX: Int, cellY: Int): Long =
sheetID.toLong().shl(32) or cellX.toLong().shl(16) or cellY.toLong()
fun packCell(sheetID: Int, cellX: Int, cellY: Int, cellPixmap: Pixmap) {
val w = cellPixmap.width
val h = cellPixmap.height
/** Scans the cell for its non-transparent bounding box, crops, and queues for deferred packing. */
fun queueCell(sheetID: Int, cellX: Int, cellY: Int, cellPixmap: Pixmap) {
var minX = cellPixmap.width
var minY = cellPixmap.height
var maxX = -1
var maxY = -1
if (cursorX + w > atlasWidth) {
cursorX = 0
cursorY += shelfHeight
shelfHeight = 0
for (y in 0 until cellPixmap.height) {
for (x in 0 until cellPixmap.width) {
if (cellPixmap.getPixel(x, y) and 0xFF != 0) {
if (x < minX) minX = x
if (y < minY) minY = y
if (x > maxX) maxX = x
if (y > maxY) maxY = y
}
}
}
pixmap.drawPixmap(cellPixmap, cursorX, cursorY)
if (maxX < 0) return // entirely transparent, skip
regions[atlasKey(sheetID, cellX, cellY)] = AtlasRegion(cursorX, cursorY, w, h)
val cropW = maxX - minX + 1
val cropH = maxY - minY + 1
val cropped = Pixmap(cropW, cropH, Pixmap.Format.RGBA8888)
cropped.drawPixmap(cellPixmap, 0, 0, minX, minY, cropW, cropH)
cursorX += w
if (h > shelfHeight) shelfHeight = h
pendingCells.add(PendingCell(sheetID, cellX, cellY, cropped, minX, minY))
}
/** Sorts queued cells by height desc then width desc, and packs into shelves. */
fun packAllQueued() {
pendingCells.sortWith(
compareByDescending<PendingCell> { it.cropped.height }
.thenByDescending { it.cropped.width }
)
for (cell in pendingCells) {
val w = cell.cropped.width
val h = cell.cropped.height
// start new shelf if cell doesn't fit horizontally
if (cursorX + w > atlasWidth) {
cursorX = 0
cursorY += shelfHeight
shelfHeight = 0
}
pixmap.drawPixmap(cell.cropped, cursorX, cursorY)
regions[atlasKey(cell.sheetID, cell.cellX, cell.cellY)] =
AtlasRegion(cursorX, cursorY, w, h, cell.offsetX, cell.offsetY)
cursorX += w
if (h > shelfHeight) shelfHeight = h
cell.cropped.dispose()
}
pendingCells.clear()
}
fun blitSheet(sheetID: Int, sheetPixmap: Pixmap, cellW: Int, cellH: Int, cols: Int, rows: Int) {

View File

@@ -386,9 +386,19 @@ class TerrarumSansBitmap(
PixmapRegionPack(pixmap, W_LATIN_WIDE, H)
else throw IllegalArgumentException("Unknown sheet index: $index")
// this code causes initial deva chars to be skipped from rendering
// val illegalCells = HashSet<Long>()
// for (code in codeRange[index]) {
// if (glyphProps[code]?.isIllegal == true) {
// val pos = getSheetwisePosition(0, code)
// illegalCells.add(pos[0].toLong().shl(16) or pos[1].toLong())
// }
// }
//
for (cy in 0 until texRegPack.verticalCount) {
for (cx in 0 until texRegPack.horizontalCount) {
atlas.packCell(index, cx, cy, texRegPack.get(cx, cy))
// if (cx.toLong().shl(16) or cy.toLong() in illegalCells) continue
atlas.queueCell(index, cx, cy, texRegPack.get(cx, cy))
}
}
@@ -397,6 +407,9 @@ class TerrarumSansBitmap(
}
}
// sort and pack all queued cells (tight-cropped, sorted by height then width)
atlas.packAllQueued()
// pack wenquanyi (SHEET_UNIHAN) last as a contiguous blit
unihanPixmap?.let {
val cols = it.width / W_UNIHAN
@@ -566,6 +579,9 @@ class TerrarumSansBitmap(
renderCol = getColour(c)
}
}
else if (isNoDrawChar(c) || glyphProps[c]?.isIllegal == true) {
// whitespace/control/internal/invalid — no visible glyph, just advance position
}
else if (sheetID == SHEET_HANGUL) {
// Flookahead for {I, P, F}
@@ -674,6 +690,9 @@ class TerrarumSansBitmap(
renderCol = getColour(c)
}
}
else if (isNoDrawChar(c) || glyphProps[c]?.isIllegal == true) {
// whitespace/control/internal/invalid — no visible glyph, just advance position
}
else if (sheetID == SHEET_HANGUL) {
// Flookahead for {I, P, F}
@@ -862,6 +881,7 @@ class TerrarumSansBitmap(
SHEET_LATIN_EXTE_VARW -> latinExtEIndexY(ch)
SHEET_LATIN_EXTF_VARW -> latinExtFIndexY(ch)
SHEET_LATIN_EXTG_VARW -> latinExtGIndexY(ch)
SHEET_OGHAM_VARW -> oghamIndexY(ch)
else -> ch / 16
}
@@ -2593,6 +2613,7 @@ class TerrarumSansBitmap(
internal const val SHEET_LATIN_EXTE_VARW = 45
internal const val SHEET_LATIN_EXTF_VARW = 46
internal const val SHEET_LATIN_EXTG_VARW = 47
internal const val SHEET_OGHAM_VARW = 48
internal const val SHEET_UNKNOWN = 254
@@ -2663,6 +2684,7 @@ class TerrarumSansBitmap(
"latinExtE_variable.tga",
"latinExtF_variable.tga",
"latinExtG_variable.tga",
"ogham_variable.tga",
)
internal val codeRange = arrayOf( // MUST BE MATCHING WITH SHEET INDICES!!
0..0xFF, // SHEET_ASCII_VARW
@@ -2713,6 +2735,7 @@ class TerrarumSansBitmap(
0xAB30..0xAB6F, // SHEET_LATIN_EXTE_VARW
0x10780..0x107BF, // SHEET_LATIN_EXTF_VARW
0x1DF00..0x1DFFF, // SHEET_LATIN_EXTG_VARW
0x1680..0x169F, // SHEET_OGHAM_VARW
)
private val codeRangeHangulCompat = 0x3130..0x318F
@@ -3008,6 +3031,13 @@ class TerrarumSansBitmap(
private fun isBulgarian(c: CodePoint) = c in 0xF0000..0xF005F
private fun isSerbian(c: CodePoint) = c in 0xF0060..0xF00BF
fun isColourCode(c: CodePoint) = c == 0x100000 || c in 0x10F000..0x10FFFF
private fun isNoDrawChar(c: CodePoint): Boolean =
c <= 0x20 || c == NBSP || c == SHY || c == OBJ ||
c in 0x2000..0x200D ||
c in 0xD800..0xDFFF ||
c in 0xF800..0xF8FF ||
c in 0xFFF70..0xFFF9F ||
c >= 0xFFFA0
private fun isCharsetOverride(c: CodePoint) = c in 0xFFFC0..0xFFFCF
private fun isDevanagari(c: CodePoint) = c in codeRange[SHEET_DEVANAGARI_VARW]
private fun isHangulCompat(c: CodePoint) = c in codeRangeHangulCompat
@@ -3064,6 +3094,7 @@ class TerrarumSansBitmap(
private fun latinExtEIndexY(c: CodePoint) = (c - 0xAB30) / 16
private fun latinExtFIndexY(c: CodePoint) = (c - 0x10780) / 16
private fun latinExtGIndexY(c: CodePoint) = (c - 0x1DF00) / 16
private fun oghamIndexY(c: CodePoint) = (c - 0x1680) / 16
val charsetOverrideDefault = Character.toChars(CHARSET_OVERRIDE_DEFAULT).toSurrogatedString()
val charsetOverrideBulgarian = Character.toChars(CHARSET_OVERRIDE_BG_BG).toSurrogatedString()

BIN
work_files/ogham_variable.kra LFS Normal file

Binary file not shown.