11 Commits

Author SHA1 Message Date
minjaesong
39603d897b more fixes 2026-03-08 03:12:40 +09:00
minjaesong
2b1f9a866f more fixes 2026-03-08 02:38:03 +09:00
minjaesong
af1d720ec2 anchor fixes 2026-03-08 01:44:17 +09:00
minjaesong
8a52fcfb91 tfw part of your code was full of hacks 2026-03-08 00:32:42 +09:00
minjaesong
b82dbecc30 keming machine dot removal directive for OTF 2026-03-07 22:47:01 +09:00
minjaesong
2008bbf6dd keming machine dot removal directive 2026-03-07 22:41:24 +09:00
minjaesong
163e3d7b3e Arrow and Number Forms unicode block 2026-03-07 22:13:22 +09:00
minjaesong
f26998b641 Cyrillic Ext-C 2026-03-07 21:34:40 +09:00
minjaesong
4121648dfc minor fixes (2) 2026-03-07 21:08:44 +09:00
minjaesong
30de279a08 minor fixes 2026-03-07 21:03:10 +09:00
minjaesong
956599b83f Full Cyrillic, CyrlExtA/B support (enables church slavonic) 2026-03-07 20:55:47 +09:00
36 changed files with 257 additions and 116 deletions

Binary file not shown.

View File

@@ -46,8 +46,8 @@ Rightmost vertical column (should be 20 px tall) contains the tags. Tags are def
W |= Width of the character
W |
W -'
m --Is this character lowheight?
K -,
K |
K |= Tags used by the "Keming Machine"
K -'
Q ---Compiler Directive (see below)
@@ -77,29 +77,32 @@ Up&Down:
<MSB,Red> SXXXXXXX SYYYYYYY 00000000 <LSB,Blue>
Each X and Y numbers are Signed 8-Bit Integer.
Each X and Y numbers are TWO'S COMPLEMENT Signed 8-Bit Integer.
X-positive: nudges towards left
Y-positive: nudges towards up
Y-positive: nudges towards down
#### Diacritics Anchor Point Encoding
4 Pixels are further divided as follows:
| LSB | | Red | Green | Blue |
| ------------ | ------------ | ------------ | ------------ | ------------ |
| ------------ | ------------ | ------------ | ----------- | ------------ |
| Y | Anchor point Y for: | undefined | undefined | undefined |
| X | Anchor point X for: | undefined | undefined | undefined |
| Y | Anchor point Y for: | (unused) | (unused) | (unused) |
| Y | Anchor point Y for: | Type-0 | Type-1 | Type-2 |
| X | Anchor point X for: | Type-0 | Type-1 | Type-2 |
| **MSB** | | | | |
<MSB,Red> 1Y1Y1Y1Y 1Y2Y2Y2Y 1Y3Y3Y3Y <LSB,Blue>
<MSB,Red> 1X1X1X1X 1X2X2X2X 1X3X3X3X <LSB,Blue>
<MSB,Red> 1Y1Y1Y1Y 2Y2Y2Y2Y 3Y3Y3Y3Y <LSB,Blue>
<MSB,Red> 1X1X1X1X 2X2X2X2X 3X3X3X3X <LSB,Blue>
where Red is first, Green is second, Blue is the third diacritics.
MSB for each word must be set so that the pixel would appear brighter on the image editor.
(the font program will only read low 7 bits for each RGB channel)
Each X and Y numbers are SIGN AND MAGNITUDE 8-Bit Integer.
X-positive: nudges towards left
Y-positive: nudges towards down
#### Diacritics Type Bit Encoding

Binary file not shown.

View File

@@ -370,10 +370,9 @@ def build_font(assets_dir, output_path, no_bitmap=False, no_features=False):
x_offset = 0
x_offset -= g.props.nudge_x * SCALE
# For STACK_DOWN marks (below-base diacritics), negative nudge_y
# means "shift content down to below baseline". The sign convention
# is opposite to non-marks where positive nudge_y means shift down.
if g.props.stack_where == SC.STACK_DOWN and g.props.write_on_top >= 0:
# For marks (write_on_top >= 0), positive nudge_y means shift UP
# in the bitmap engine (opposite to non-marks where positive = down).
if g.props.write_on_top >= 0:
y_offset = g.props.nudge_y * SCALE
else:
y_offset = -g.props.nudge_y * SCALE

View File

@@ -38,6 +38,7 @@ class GlyphProps:
has_kern_data: bool = False
is_kern_y_type: bool = False
kerning_mask: int = 255
dot_removal: Optional[int] = None # codepoint to replace with when followed by a STACK_UP mark
directive_opcode: int = 0
directive_arg1: int = 0
directive_arg2: int = 0
@@ -131,7 +132,8 @@ def parse_variable_sheet(image, sheet_index, cell_w, cell_h, cols, is_xy_swapped
# Kerning data
kerning_bit1 = _tagify(image.get_pixel(code_start_x, code_start_y + 6))
# kerning_bit2 and kerning_bit3 are reserved
kerning_bit2 = _tagify(image.get_pixel(code_start_x, code_start_y + 7))
dot_removal = None if kerning_bit2 == 0 else (kerning_bit2 >> 8)
is_kern_y_type = (kerning_bit1 & 0x80000000) != 0
kerning_mask = (kerning_bit1 >> 8) & 0xFFFFFF
has_kern_data = (kerning_bit1 & 0xFF) != 0
@@ -188,7 +190,7 @@ def parse_variable_sheet(image, sheet_index, cell_w, cell_h, cols, is_xy_swapped
align_where=align_where, write_on_top=write_on_top,
stack_where=stack_where, ext_info=ext_info,
has_kern_data=has_kern_data, is_kern_y_type=is_kern_y_type,
kerning_mask=kerning_mask,
kerning_mask=kerning_mask, dot_removal=dot_removal,
directive_opcode=directive_opcode, directive_arg1=directive_arg1,
directive_arg2=directive_arg2,
)

View File

@@ -70,6 +70,11 @@ languagesystem sund dflt;
if ccmp_code:
parts.append(ccmp_code)
# ccmp: dot removal (e.g. i→ı, j→ȷ when followed by STACK_UP marks)
dot_removal_code = _generate_dot_removal(glyphs, has)
if dot_removal_code:
parts.append(dot_removal_code)
# Hangul jamo GSUB assembly
hangul_code = _generate_hangul_gsub(glyphs, has, jamo_data)
if hangul_code:
@@ -162,6 +167,54 @@ def _generate_ccmp(replacewith_subs, has):
return '\n'.join(lines)
def _generate_dot_removal(glyphs, has):
"""Generate ccmp contextual substitution for dot removal.
When a base glyph tagged with dot_removal (kerning bit 2, pixel Y+7) is
followed by a STACK_UP mark, substitute the base with its dotless form.
Matches the Kotlin engine's dotRemoval logic.
"""
# Collect all STACK_UP marks
stack_up_marks = []
for cp, g in glyphs.items():
if g.props.write_on_top >= 0 and g.props.stack_where == SC.STACK_UP and has(cp):
stack_up_marks.append(cp)
if not stack_up_marks:
return ""
# Collect all base glyphs with dot_removal
dot_removal_subs = []
for cp, g in glyphs.items():
if g.props.dot_removal is not None and has(cp) and has(g.props.dot_removal):
dot_removal_subs.append((cp, g.props.dot_removal))
if not dot_removal_subs:
return ""
lines = []
# Define the STACK_UP marks class
mark_names = ' '.join(glyph_name(cp) for cp in sorted(stack_up_marks))
lines.append(f"@stackUpMarks = [{mark_names}];")
lines.append("")
# Single substitution lookup for the replacements
lines.append("lookup DotRemoval {")
for src_cp, dst_cp in sorted(dot_removal_subs):
lines.append(f" sub {glyph_name(src_cp)} by {glyph_name(dst_cp)};")
lines.append("} DotRemoval;")
lines.append("")
# Contextual rules in ccmp
lines.append("feature ccmp {")
for src_cp, _ in sorted(dot_removal_subs):
lines.append(f" sub {glyph_name(src_cp)}' lookup DotRemoval @stackUpMarks;")
lines.append("} ccmp;")
return '\n'.join(lines)
def _generate_hangul_gsub(glyphs, has, jamo_data):
"""
Generate Hangul jamo GSUB lookups for syllable assembly.
@@ -1750,7 +1803,7 @@ def _generate_mark(glyphs, has):
mark_groups = {} # (mark_type, align, is_dia, stack_cat) -> [(cp, g), ...]
for cp, g in marks.items():
is_dia = (0x0300 <= cp <= 0x036F)
is_dia = True # all marks (write_on_top >= 0) are diacritics; Kotlin applies lowheight shiftdown unconditionally
sc = _stack_cat(g.props.stack_where)
key = (g.props.write_on_top, g.props.align_where, is_dia, sc)
mark_groups.setdefault(key, []).append((cp, g))
@@ -1846,7 +1899,9 @@ def _generate_mark(glyphs, has):
# Lowheight adjustment for combining diacritical marks:
# shift base anchor Y down so diacritics sit closer to
# the shorter base glyph.
if is_dia and g.props.is_low_height:
# Only applies to 'up' marks (and overlay), not 'dn',
# matching Kotlin which only adjusts in STACK_UP/STACK_UP_N_DOWN.
if is_dia and g.props.is_low_height and scat != 'dn':
if mark_type == 2: # overlay
ay -= SC.H_OVERLAY_LOWERCASE_SHIFTDOWN * SC.SCALE
else: # above (type 0)
@@ -1878,12 +1933,13 @@ def _generate_mark(glyphs, has):
lines.append(f"lookup {mkmk_name} {{")
if scat == 'up':
m2y = SC.ASCENT + SC.H_DIACRITICS * SC.SCALE
m2y_base = SC.ASCENT + SC.H_DIACRITICS * SC.SCALE
else: # 'dn'
m2y = SC.ASCENT - SC.H_DIACRITICS * SC.SCALE
m2y_base = SC.ASCENT - SC.H_DIACRITICS * SC.SCALE
for cp, g in mark_list:
mx = mark_anchors.get(cp, 0)
m2y = m2y_base
lines.append(
f" pos mark {glyph_name(cp)}"
f" <anchor {mx} {m2y}> mark {class_name};"
@@ -1893,6 +1949,46 @@ def _generate_mark(glyphs, has):
lines.append("")
mkmk_lookup_names.append(mkmk_name)
# --- Nudge-Y gap correction for MarkToMark ---
# Without cascade, the gap between consecutive 'up' marks is
# H_DIACRITICS + nudge_y_2 - nudge_y_1
# which is less than H_DIACRITICS when nudge_y_1 > nudge_y_2
# (e.g. Cyrillic uni2DED nudge=2 followed by uni0487 nudge=0).
# Add contextual positioning to compensate: shift mark2 up by
# (nudge_y_1 - nudge_y_2) * SCALE for each such pair.
# This keeps Thai correct (same nudge on both marks → no correction)
# while fixing Cyrillic (different nudge → correction applied).
nudge_groups = {} # nudge_y -> [glyph_name, ...]
for (mark_type, align, is_dia, scat), mark_list in sorted(mark_groups.items()):
if scat != 'up':
continue
for cp, g in mark_list:
ny = g.props.nudge_y
nudge_groups.setdefault(ny, []).append(glyph_name(cp))
distinct_nudges = sorted(nudge_groups.keys())
correction_pairs = []
for n1 in distinct_nudges:
for n2 in distinct_nudges:
if n1 > n2:
correction_pairs.append((n1, n2, (n1 - n2) * SC.SCALE))
if correction_pairs:
for ny, glyphs in sorted(nudge_groups.items()):
lines.append(f"@up_nudge_{ny} = [{' '.join(sorted(glyphs))}];")
lines.append("")
mkmk_corr_name = "mkmk_nudge_correct"
lines.append(f"lookup {mkmk_corr_name} {{")
for n1, n2, val in correction_pairs:
lines.append(
f" pos @up_nudge_{n1} <0 0 0 0>"
f" @up_nudge_{n2} <0 {val} 0 0>;"
)
lines.append(f"}} {mkmk_corr_name};")
lines.append("")
mkmk_lookup_names.append(mkmk_corr_name)
# Register MarkToBase lookups under mark.
# dev2 is excluded: HarfBuzz/DirectWrite use abvm for Devanagari marks.
# deva is INCLUDED: CoreText's old-Indic shaper may need mark/mkmk

View File

@@ -72,6 +72,9 @@ SHEET_ALPHABETIC_PRESENTATION_FORMS = 38
SHEET_HENTAIGANA_VARW = 39
SHEET_CONTROL_PICTURES_VARW = 40
SHEET_LEGACY_COMPUTING_VARW = 41
SHEET_CYRILIC_EXTB_VARW = 42
SHEET_CYRILIC_EXTA_VARW = 43
SHEET_CYRILIC_EXTC_VARW = 44
SHEET_UNKNOWN = 254
@@ -118,6 +121,9 @@ FILE_LIST = [
"hentaigana_variable.tga",
"control_pictures_variable.tga",
"symbols_for_legacy_computing_variable.tga",
"cyrilic_extB_variable.tga",
"cyrilic_extA_variable.tga",
"cyrilic_extC_variable.tga",
]
CODE_RANGE = [
@@ -151,7 +157,7 @@ CODE_RANGE = [
list(range(0xA720, 0xA800)), # 27: Latin Ext D
list(range(0x20A0, 0x20D0)), # 28: Currencies
list(range(0xFFE00, 0xFFFA0)), # 29: Internal
list(range(0x2100, 0x2150)), # 30: Letterlike
list(range(0x2100, 0x2200)), # 30: Letterlike
list(range(0x1F100, 0x1F200)), # 31: Enclosed Alphanum Supl
list(range(0x0B80, 0x0C00)) + list(range(0xF00C0, 0xF0100)), # 32: Tamil
list(range(0x980, 0xA00)), # 33: Bengali
@@ -163,6 +169,9 @@ CODE_RANGE = [
list(range(0x1B000, 0x1B170)), # 39: Hentaigana
list(range(0x2400, 0x2440)), # 40: Control Pictures
list(range(0x1FB00, 0x1FC00)), # 41: Legacy Computing
list(range(0xA640, 0xA6A0)), # 42: Cyrillic Ext B
list(range(0x2DE0, 0x2E00)), # 43: Cyrillic Ext A
list(range(0x1C80, 0x1C8F)), # 43: Cyrillic Ext C
]
CODE_RANGE_HANGUL_COMPAT = range(0x3130, 0x3190)
@@ -539,5 +548,8 @@ def index_y(sheet_index, c):
SHEET_HENTAIGANA_VARW: lambda: (c - 0x1B000) // 16,
SHEET_CONTROL_PICTURES_VARW: lambda: (c - 0x2400) // 16,
SHEET_LEGACY_COMPUTING_VARW: lambda: (c - 0x1FB00) // 16,
SHEET_CYRILIC_EXTB_VARW: lambda: (c - 0xA640) // 16,
SHEET_CYRILIC_EXTA_VARW: lambda: (c - 0x2DE0) // 16,
SHEET_CYRILIC_EXTC_VARW: lambda: (c - 0x1C80) // 16,
SHEET_HANGUL: lambda: 0,
}.get(sheet_index, lambda: c // 16)()

Binary file not shown.

BIN
demo.PNG

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 177 KiB

View File

@@ -25,6 +25,7 @@ How multilingual? Real multilingual!
􏻬আমি কাঁচ খেতে পারি, তাতে আমার কোনো ক্ষতি হয় না। 􀀀
􏻬󿿁Под южно дърво, цъфтящо в синьо, бягаше малко пухкаво зайче󿿀􀀀
􏻬ᎠᏍᎦᏯᎡᎦᎢᎾᎨᎢᎣᏍᏓᎤᎩᏍᏗᎥᎴᏓᎯᎲᎢᏔᎵᏕᎦᏟᏗᏖᎸᎳᏗᏗᎧᎵᎢᏘᎴᎩ ᏙᏱᏗᏜᏫᏗᏣᏚᎦᏫᏛᏄᏓᎦᏝᏃᎠᎾᏗᎭᏞᎦᎯᎦᏘᏓᏠᎨᏏᏕᏡᎬᏢᏓᏥᏩᏝᎡᎢᎪᎢ ᎠᎦᏂᏗᎮᎢᎫᎩᎬᏩᎴᎢᎠᏆᏅᏛᎫᏊᎾᎥᎠᏁᏙᎲᏐᏈᎵᎤᎩᎸᏓᏭᎷᏤᎢᏏᏉᏯᏌᏊ ᎤᏂᏋᎢᏡᎬᎢᎰᏩᎬᏤᎵᏍᏗᏱᎩᎱᎱᎤᎩᎴᎢᏦᎢᎠᏂᏧᏣᏨᎦᏥᎪᎥᏌᏊᎤᎶᏒᎢᎢᏡᎬᎢ ᎹᎦᎺᎵᏥᎻᎼᏏᎽᏗᏩᏂᎦᏘᎾᎿᎠᏁᎬᎢᏅᎩᎾᏂᎡᎢᏌᎶᎵᏎᎷᎠᏑᏍᏗᏪᎩ ᎠᎴ ᏬᏗᏲᏭᎾᏓᏍᏓᏴᏁᎢᎤᎦᏅᏮᏰᎵᏳᏂᎨᎢ􀀀
􏻬Ѳеѡфа́нъ и҆ Алеѯі́й, ѕѣлѡ̀ возлюби́вше ѱалти́рь, воспѣ́ша при свѣ́тѣ ѕвѣ́здъ, помазꙋ́юще сщ҃е́нное мѵ́ро; серафими мн̑оꙮ҆читїи̑, ꙗ҆́кѡ ѻ҆́гнь, ѡ҆крꙋжа́хꙋ прⷭ҇то́лъ Бж҃їй, и҆ всѧ̀ землѧ̀ и҆спо́лнисѧ свѣ́та, ꙗ҆́кѡ ѕмі́й попра́нъ є҆́сть􀀀
􏻬Příliš žluťoučký kůň úpěl ďábelské ódy􀀀
􏻬Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Walther spillede på xylofon􀀀
􏻬PACK MY BOX WITH FIVE DOZEN LIQUOR JUGS􀀀
@@ -104,6 +105,10 @@ How multilingual? Real multilingual!
􎳌‣ Full support for Archaic Kana/Hentaigana􀀀
􏻬серафими мн̑оꙮ҆читїи̑, ꙗ҆́кѡ ѻ҆́гнь, ѡ҆крꙋжа́хꙋ прⷭ҇то́лъ Бж҃їй, и҆ всѧ̀ землѧ̀ и҆спо́лнисѧ свѣ́та􀀀
􎳌‣ Fan of Church Slavonic? Weve got you!􀀀
􏃯Supported Unicode Blocks:􀀀
Basic Latin
@@ -111,6 +116,7 @@ How multilingual? Real multilingual!
Latin Extended Additional
Latin Extended-A/B/C/D
Armenian
Arrows
Bengali􏿆ᶠⁱ􀀀
Braille Patterns
Cherokee􏿆⁷􀀀
@@ -120,8 +126,9 @@ How multilingual? Real multilingual!
Combining Diacritical Marks
Control Pictures
Currency Symbols
Cyrillic􏿆ᴭ􀀀
Cyrillic Supplement􏿆ᴭ􀀀
Cyrillic
Cyrillic Supplement
Cyrillic Extended-A/B/C
Devanagari
Enclosed Alphanumeric Supplement
General Punctuations
@@ -140,6 +147,7 @@ How multilingual? Real multilingual!
Katakana Phonetic Extensions
Kana Supplement
Kana Extended-A
Number Forms
Small Kana Extension
Letterlike Symbols
Phonetic Extensions
@@ -153,7 +161,7 @@ How multilingual? Real multilingual!
Tamil
Thai
􏿆ᴭ􀀀 No support for archæic letters  􏿆ᴱ􀀀 No support for Coptic
􏿆ᴱ􀀀 No support for Coptic
􏿆ᶠⁱ􀀀 No support for ligatures  􏿆ჼ􀀀 Mkhedruli only
􏿆⁶􀀀 􏿆⁷􀀀 􏿆⁹􀀀 􏿆¹²·¹􀀀 Up to the specified Unicode version

View File

@@ -1,7 +1,7 @@
--- 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
- bit must be set if above-diacritics should be lowered (e.g. lowercase b, which has 'A' shape bit but considered lowheight)
### Legends
#
@@ -106,3 +106,5 @@ dot removal for diacritics:
- encoding:
- <MSB> RRRRRRRR GGGGGGGG BBBBBBBB <LSB>
--- Pixel 3
Unused for now.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,9 +1,13 @@
package net.torvald.terrarumsansbitmap
import net.torvald.terrarumsansbitmap.gdx.CodePoint
/**
* Created by minjaesong on 2021-11-25.
*/
data class DiacriticsAnchor(val type: Int, val x: Int, val y: Int, val xUsed: Boolean, val yUsed: Boolean)
data class DiacriticsAnchor(val type: Int, val x: Int, val y: Int) {
val isZero = (x == 0 && y == 0)
}
/**
* Created by minjaesong on 2018-08-07.
*/
@@ -15,7 +19,7 @@ data class GlyphProps(
val nudgeX: Int = 0,
val nudgeY: Int = 0,
val diacriticsAnchors: Array<DiacriticsAnchor> = Array(6) { DiacriticsAnchor(it, 0, 0, false, false) },
val diacriticsAnchors: Array<DiacriticsAnchor> = Array(6) { DiacriticsAnchor(it, 0, 0) },
val alignWhere: Int = 0, // ALIGN_LEFT..ALIGN_BEFORE
@@ -29,6 +33,8 @@ data class GlyphProps(
val isKernYtype: Boolean = false,
val kerningMask: Int = 255,
val dotRemoval: CodePoint? = null,
val directiveOpcode: Int = 0, // 8-bits wide
val directiveArg1: Int = 0, // 8-bits wide
val directiveArg2: Int = 0, // 8-bits wide
@@ -95,10 +101,6 @@ data class GlyphProps(
diacriticsAnchors.forEach {
hash = hash xor it.type
hash = hash * 16777619
hash = hash xor (it.x or (if (it.xUsed) 128 else 0))
hash = hash * 16777619
hash = hash xor (it.y or (if (it.yUsed) 128 else 0))
hash = hash * 16777619
}
hash = hash xor tags

View File

@@ -888,6 +888,9 @@ class TerrarumSansBitmap(
SHEET_HENTAIGANA_VARW -> hentaiganaIndexY(ch)
SHEET_CONTROL_PICTURES_VARW -> controlPicturesIndexY(ch)
SHEET_LEGACY_COMPUTING_VARW -> legacyComputingIndexY(ch)
SHEET_CYRILIC_EXTB_VARW -> cyrilicExtBIndexY(ch)
SHEET_CYRILIC_EXTA_VARW -> cyrilicExtAIndexY(ch)
SHEET_CYRILIC_EXTC_VARW -> cyrilicExtCIndexY(ch)
else -> ch / 16
}
@@ -918,9 +921,9 @@ class TerrarumSansBitmap(
val isLowHeight = (pixmap.getPixel(codeStartX, codeStartY + 5).and(255) != 0)
// Keming machine parameters
val kerningBit1 = pixmap.getPixel(codeStartX, codeStartY + 6).tagify()
val kerningBit2 = pixmap.getPixel(codeStartX, codeStartY + 7).tagify()
val kerningBit3 = pixmap.getPixel(codeStartX, codeStartY + 8).tagify()
val kerningBit1 = pixmap.getPixel(codeStartX, codeStartY + 6).tagify() // glyph shape
val kerningBit2 = pixmap.getPixel(codeStartX, codeStartY + 7).tagify() // dot removal
val kerningBit3 = pixmap.getPixel(codeStartX, codeStartY + 8).tagify() // unused
var isKernYtype = ((kerningBit1 and 0x80000000.toInt()) != 0)
var kerningMask = kerningBit1.ushr(8).and(0xFFFFFF)
val hasKernData = kerningBit1 and 255 != 0//(kerningBit1 and 255 != 0 && kerningMask != 0xFFFF)
@@ -944,12 +947,12 @@ class TerrarumSansBitmap(
val shift = (3 - (it % 3)) * 8
val yPixel = pixmap.getPixel(codeStartX, codeStartY + yPos).tagify()
val xPixel = pixmap.getPixel(codeStartX, codeStartY + yPos + 1).tagify()
val yUsed = (yPixel ushr shift) and 128 != 0
val xUsed = (xPixel ushr shift) and 128 != 0
val y = if (yUsed) (yPixel ushr shift) and 127 else 0
val x = if (xUsed) (xPixel ushr shift) and 127 else 0
val ySgn = ((yPixel ushr shift) and 128).let { if (it == 0) -1 else 1 }
val xSgn = ((xPixel ushr shift) and 128).let { if (it == 0) -1 else 1 }
val y = ((yPixel ushr shift) and 127) * ySgn
val x = ((xPixel ushr shift) and 127) * xSgn
DiacriticsAnchor(it, x, y, xUsed, yUsed)
DiacriticsAnchor(it, x, y)
}.toTypedArray()
val alignWhere = (0..1).fold(0) { acc, y -> acc or ((pixmap.getPixel(codeStartX, codeStartY + y + 15).and(255) != 0).toInt() shl y) }
@@ -968,7 +971,9 @@ class TerrarumSansBitmap(
GlyphProps.STACK_DONT
else (0..1).fold(0) { acc, y -> acc or ((pixmap.getPixel(codeStartX, codeStartY + y + 18).and(255) != 0).toInt() shl y) }
glyphProps[code] = GlyphProps(width, isLowHeight, nudgeX, nudgeY, diacriticsAnchors, alignWhere, writeOnTop, stackWhere, IntArray(15), hasKernData, isKernYtype, kerningMask, directiveOpcode, directiveArg1, directiveArg2)
val dotRemoval = if (kerningBit2 == 0) null else kerningBit2.ushr(8)
glyphProps[code] = GlyphProps(width, isLowHeight, nudgeX, nudgeY, diacriticsAnchors, alignWhere, writeOnTop, stackWhere, IntArray(15), hasKernData, isKernYtype, kerningMask, dotRemoval, directiveOpcode, directiveArg1, directiveArg2)
// extra info
val extCount = glyphProps[code]?.requiredExtInfoCount() ?: 0
@@ -1015,17 +1020,6 @@ class TerrarumSansBitmap(
(0xD800..0xDFFF).forEach { glyphProps[it] = GlyphProps(0) }
(0x100000..0x10FFFF).forEach { glyphProps[it] = GlyphProps(0) }
(0xFFFA0..0xFFFFF).forEach { glyphProps[it] = GlyphProps(0) }
// manually add width of one orphan insular letter
// WARNING: glyphs in 0xA770..0xA778 has invalid data, further care is required
glyphProps[0x1D79] = GlyphProps(9)
// 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)
}
private fun Int.halveWidth() = this / 2 + 1
@@ -1120,6 +1114,7 @@ class TerrarumSansBitmap(
var nonDiacriticCounter = 0 // index of last instance of non-diacritic char
var stackUpwardCounter = 0 // TODO separate stack counter for centre- and right aligned
var stackDownwardCounter = 0
var nudgeUpHighWater = 0 // tracks max nudgeY seen in current stack, so subsequent marks with lower nudge still clear the previous one
val HALF_VAR_INIT = W_VAR_INIT.minus(1).div(2)
@@ -1198,6 +1193,7 @@ class TerrarumSansBitmap(
stackUpwardCounter = 0
stackDownwardCounter = 0
nudgeUpHighWater = 0
}
// FIXME HACK: using 0th diacritics' X-anchor pos as a type selector
/*else if (thisProp.writeOnTop && thisProp.diacriticsAnchors[0].x == GlyphProps.DIA_JOINER) {
@@ -1217,30 +1213,32 @@ class TerrarumSansBitmap(
// set X pos according to alignment information
posXbuffer[charIndex] = -thisProp.nudgeX +
when (thisProp.alignWhere) {
GlyphProps.ALIGN_LEFT, GlyphProps.ALIGN_BEFORE -> posXbuffer[nonDiacriticCounter]
GlyphProps.ALIGN_LEFT, GlyphProps.ALIGN_BEFORE -> {
val anchorPointX = if (itsProp.diacriticsAnchors[diacriticsType].isZero) itsProp.width else itsProp.diacriticsAnchors[diacriticsType].x
posXbuffer[nonDiacriticCounter] + anchorPointX
}
GlyphProps.ALIGN_RIGHT -> {
// println("thisprop alignright $kerning, $extraWidth")
val anchorPoint =
if (!itsProp.diacriticsAnchors[diacriticsType].xUsed) itsProp.width else itsProp.diacriticsAnchors[diacriticsType].x
val anchorPointX = if (itsProp.diacriticsAnchors[diacriticsType].isZero) itsProp.width else itsProp.diacriticsAnchors[diacriticsType].x
extraWidth += thisProp.width
posXbuffer[nonDiacriticCounter] + anchorPoint - W_VAR_INIT + kerning + extraWidth
posXbuffer[nonDiacriticCounter] + anchorPointX - W_VAR_INIT + kerning + extraWidth
}
GlyphProps.ALIGN_CENTRE -> {
val anchorPoint =
if (!itsProp.diacriticsAnchors[diacriticsType].xUsed) itsProp.width.div(2) else itsProp.diacriticsAnchors[diacriticsType].x
val anchorPointX = if (itsProp.diacriticsAnchors[diacriticsType].isZero) itsProp.width.div(2) else itsProp.diacriticsAnchors[diacriticsType].x
if (itsProp.alignWhere == GlyphProps.ALIGN_RIGHT) {
if (thisChar in 0x900..0x902)
posXbuffer[nonDiacriticCounter] + anchorPoint + (itsProp.width - 1).div(2)
posXbuffer[nonDiacriticCounter] + anchorPointX + (itsProp.width - 1).div(2)
else
posXbuffer[nonDiacriticCounter] + anchorPoint + (itsProp.width + 1).div(2)
posXbuffer[nonDiacriticCounter] + anchorPointX + (itsProp.width + 1).div(2)
} else {
if (thisChar in 0x900..0x902)
posXbuffer[nonDiacriticCounter] + anchorPoint - (W_VAR_INIT + 1) / 2
posXbuffer[nonDiacriticCounter] + anchorPointX - (W_VAR_INIT + 1) / 2
else
posXbuffer[nonDiacriticCounter] + anchorPoint - HALF_VAR_INIT
posXbuffer[nonDiacriticCounter] + anchorPointX - HALF_VAR_INIT
}
}
else -> throw InternalError("Unsupported alignment: ${thisProp.alignWhere}")
@@ -1277,14 +1275,14 @@ class TerrarumSansBitmap(
// set Y pos according to diacritics position
when (thisProp.stackWhere) {
GlyphProps.STACK_DOWN -> {
posYbuffer[charIndex] = (-thisProp.nudgeY + H_DIACRITICS * stackDownwardCounter) * flipY.toSign()
posYbuffer[charIndex] = (-thisProp.nudgeY + H_DIACRITICS * stackDownwardCounter) * flipY.toSign() - thisProp.nudgeY
stackDownwardCounter++
}
GlyphProps.STACK_UP -> {
posYbuffer[charIndex] = -thisProp.nudgeY + (-H_DIACRITICS * stackUpwardCounter + -thisProp.nudgeY) * flipY.toSign()
val effectiveNudge = maxOf(thisProp.nudgeY, nudgeUpHighWater)
posYbuffer[charIndex] = -effectiveNudge + (-H_DIACRITICS * stackUpwardCounter + -effectiveNudge) * flipY.toSign() + effectiveNudge
// shift down on lowercase if applicable
if (getSheetType(thisChar) in autoShiftDownOnLowercase &&
lastNonDiacriticChar.isLowHeight()) {
if (lastNonDiacriticChar.isLowHeight()) {
//dbgprn("AAARRRRHHHH for character ${thisChar.toHex()}")
//dbgprn("lastNonDiacriticChar: ${lastNonDiacriticChar.toHex()}")
//dbgprn("cond: ${thisProp.alignXPos == GlyphProps.DIA_OVERLAY}, charIndex: $charIndex")
@@ -1294,6 +1292,7 @@ class TerrarumSansBitmap(
posYbuffer[charIndex] += H_STACKUP_LOWERCASE_SHIFTDOWN * flipY.toSign() // if minus-assign doesn't work, try plus-assign
}
nudgeUpHighWater = effectiveNudge
stackUpwardCounter++
// dbgprn("lastNonDiacriticChar: ${lastNonDiacriticChar.charInfo()}; stack counter: $stackUpwardCounter")
@@ -1302,22 +1301,25 @@ class TerrarumSansBitmap(
posYbuffer[charIndex] = (-thisProp.nudgeY + H_DIACRITICS * stackDownwardCounter) * flipY.toSign()
stackDownwardCounter++
posYbuffer[charIndex] = (-thisProp.nudgeY + -H_DIACRITICS * stackUpwardCounter) * flipY.toSign()
val effectiveNudge = maxOf(thisProp.nudgeY, nudgeUpHighWater)
posYbuffer[charIndex] = (-effectiveNudge + -H_DIACRITICS * stackUpwardCounter) * flipY.toSign()
// shift down on lowercase if applicable
if (getSheetType(thisChar) in autoShiftDownOnLowercase &&
lastNonDiacriticChar.isLowHeight()) {
if (lastNonDiacriticChar.isLowHeight()) {
if (diacriticsType == GlyphProps.DIA_OVERLAY)
posYbuffer[charIndex] += H_OVERLAY_LOWERCASE_SHIFTDOWN * flipY.toSign() // if minus-assign doesn't work, try plus-assign
else
posYbuffer[charIndex] += H_STACKUP_LOWERCASE_SHIFTDOWN * flipY.toSign() // if minus-assign doesn't work, try plus-assign
}
nudgeUpHighWater = effectiveNudge
stackUpwardCounter++
}
// for BEFORE_N_AFTER, do nothing in here
}
// nudge Y pos according to anchor position
posYbuffer[charIndex] -= itsProp.diacriticsAnchors[diacriticsType].y
// Don't reset extraWidth here!
}
}
@@ -1327,7 +1329,7 @@ class TerrarumSansBitmap(
if (str.isNotEmpty()) {
val lastCharProp = glyphProps[str.last()]
val penultCharProp = glyphProps[str[nonDiacriticCounter]] ?:
(if (errorOnUnknownChar) throw throw InternalError("No GlyphProps for char '${str[nonDiacriticCounter]}' " +
(if (errorOnUnknownChar) throw InternalError("No GlyphProps for char '${str[nonDiacriticCounter]}' " +
"(${str[nonDiacriticCounter].charInfo()})") else nullProp)
posXbuffer[posXbuffer.lastIndex] = posXbuffer[posXbuffer.lastIndex - 1] + // DON'T add 1 to house the shadow, it totally breaks stuffs
if (lastCharProp != null && lastCharProp.writeOnTop >= 0) {
@@ -1523,8 +1525,8 @@ class TerrarumSansBitmap(
}
// for lowercase i and j, if cNext is a diacritic that goes on top, remove the dots
else if (diacriticDotRemoval.containsKey(c) && (glyphProps[cNext]?.writeOnTop ?: -1) >= 0 && glyphProps[cNext]?.stackWhere == GlyphProps.STACK_UP) {
seq.add(diacriticDotRemoval[c]!!)
else if (glyphProps[c]!!.dotRemoval != null && (glyphProps[cNext]?.writeOnTop ?: -1) >= 0 && glyphProps[cNext]?.stackWhere == GlyphProps.STACK_UP) {
seq.add(glyphProps[c]!!.dotRemoval!!)
}
// BEGIN of tamil subsystem implementation
@@ -2604,6 +2606,9 @@ class TerrarumSansBitmap(
internal const val SHEET_HENTAIGANA_VARW = 39
internal const val SHEET_CONTROL_PICTURES_VARW = 40
internal const val SHEET_LEGACY_COMPUTING_VARW = 41
internal const val SHEET_CYRILIC_EXTB_VARW = 42
internal const val SHEET_CYRILIC_EXTA_VARW = 43
internal const val SHEET_CYRILIC_EXTC_VARW = 44
internal const val SHEET_UNKNOWN = 254
@@ -2625,10 +2630,6 @@ class TerrarumSansBitmap(
const val MOVABLE_BLOCK_1 = 0xFFFF0
private val autoShiftDownOnLowercase = arrayOf(
SHEET_DIACRITICAL_MARKS_VARW
)
private val fileList = arrayOf( // MUST BE MATCHING WITH SHEET INDICES!!
"ascii_variable.tga",
"hangul_johab.tga",
@@ -2672,6 +2673,9 @@ class TerrarumSansBitmap(
"hentaigana_variable.tga",
"control_pictures_variable.tga",
"symbols_for_legacy_computing_variable.tga",
"cyrilic_extB_variable.tga",
"cyrilic_extA_variable.tga",
"cyrilic_extC_variable.tga",
)
internal val codeRange = arrayOf( // MUST BE MATCHING WITH SHEET INDICES!!
0..0xFF, // SHEET_ASCII_VARW
@@ -2704,7 +2708,7 @@ class TerrarumSansBitmap(
0xA720..0xA7FF, // SHEET_EXTD_VARW
0x20A0..0x20CF, // SHEET_CURRENCIES_VARW
0xFFE00..0xFFF9F, // SHEET_INTERNAL_VARW
0x2100..0x214F, // SHEET_LETTERLIKE_MATHS_VARW
0x2100..0x21FF, // SHEET_LETTERLIKE_MATHS_VARW
0x1F100..0x1F1FF, // SHEET_ENCLOSED_ALPHNUM_SUPL_VARW
(0x0B80..0x0BFF) + (0xF00C0..0xF00FF), // SHEET_TAMIL_VARW
0x980..0x9FF, // SHEET_BENGALI_VARW
@@ -2716,6 +2720,9 @@ class TerrarumSansBitmap(
0x1B000..0x1B16F, // SHEET_HENTAIGANA_VARW
0x2400..0x243F, // SHEET_CONTROL_PICTURES_VARW
0x1FB00..0x1FBFF, // SHEET_LEGACY_COMPUTING_VARW
0xA640..0xA69F, // SHEET_CYRILIC_EXTB_VARW
0x2DE0..0x2DFF, // SHEET_CYRILIC_EXTA_VARW
0x1C80..0x1C8F, // SHEET_CYRILIC_EXTC_VARW
)
private val codeRangeHangulCompat = 0x3130..0x318F
@@ -2733,11 +2740,6 @@ class TerrarumSansBitmap(
0x20..0x7F,
)
private val diacriticDotRemoval = hashMapOf(
'i'.toInt() to 0x131,
'j'.toInt() to 0x237
)
internal fun Int.charInfo() = "U+${this.toString(16).padStart(4, '0').toUpperCase()}: ${Character.getName(this)}"
const val NQSP = 0x2000
@@ -3066,6 +3068,9 @@ class TerrarumSansBitmap(
private fun hentaiganaIndexY(c: CodePoint) = (c - 0x1B000) / 16
private fun controlPicturesIndexY(c: CodePoint) = (c - 0x2400) / 16
private fun legacyComputingIndexY(c: CodePoint) = (c - 0x1FB00) / 16
private fun cyrilicExtBIndexY(c: CodePoint) = (c - 0xA640) / 16
private fun cyrilicExtAIndexY(c: CodePoint) = (c - 0x2DE0) / 16
private fun cyrilicExtCIndexY(c: CodePoint) = (c - 0x1C80) / 16
val charsetOverrideDefault = Character.toChars(CHARSET_OVERRIDE_DEFAULT).toSurrogatedString()
val charsetOverrideBulgarian = Character.toChars(CHARSET_OVERRIDE_BG_BG).toSurrogatedString()

View File

@@ -229,16 +229,16 @@ class TerrarumTypewriterBitmap(
val nudgeY = nudgingBits.ushr(16).toByte().toInt() // signed 8-bit int
val diacriticsAnchors = (0..5).map {
val yPos = 11 + (it / 3) * 2
val yPos = 13 - (it / 3) * 2
val shift = (3 - (it % 3)) * 8
val yPixel = pixmap.getPixel(codeStartX, codeStartY + yPos).tagify()
val xPixel = pixmap.getPixel(codeStartX, codeStartY + yPos + 1).tagify()
val y = (yPixel ushr shift) and 127
val x = (xPixel ushr shift) and 127
val yUsed = (yPixel ushr shift) >= 128
val xUsed = (yPixel ushr shift) >= 128
val ySgn = ((yPixel ushr shift) and 128).let { if (it == 0) -1 else 1 }
val xSgn = ((xPixel ushr shift) and 128).let { if (it == 0) -1 else 1 }
val y = ((yPixel ushr shift) and 127) * ySgn
val x = ((xPixel ushr shift) and 127) * xSgn
DiacriticsAnchor(it, x, y, xUsed, yUsed)
DiacriticsAnchor(it, x, y)
}.toTypedArray()
val alignWhere = (0..1).fold(0) { acc, y -> acc or ((pixmap.getPixel(codeStartX, codeStartY + y + 15).and(255) != 0).toInt() shl y) }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.