"""Rebuild checked-in console assets. Python 3 + Pillow 12.3.0; no build-time download.
Pixels are converted from the existing project icon, never redrawn. The Chinese
bitmap is an OFL-1.1 subset of the pinned GNU Unifont source beside its licenses.
The Latin fonts share a real baseline: glyphs are never individually stretched
or aligned by their bounding boxes. Descenders retain their rows below it.
"""
from __future__ import annotations
import gzip, hashlib, json
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
ROOT = Path(__file__).resolve().parents[2]
HERE = Path(__file__).resolve().parent
FONT = ROOT / "assets/fonts/unifont-16.0.04/unifont-16.0.04.hex.gz"
FONT_SHA256 = "f9c8c7802453f47be02677176aeac2342ee96d354fad7a26cedcce48e68e1d9f"
LATIN_FONT = ROOT / "assets/fonts/cascadia-mono-2407.24/CascadiaMono-Regular.ttf"
LATIN_SHA256 = "06520d032ec274fa5040b22c6f4a1d829081b24ba40b2da56dae89bf10c7b481"
CELL_WIDTH, CELL_HEIGHT, BASELINE = 12, 20, 16
ASCII_FIRST, ASCII_LAST = 32, 127
RANGES = [(0x80, 0x7FF), (0x2000, 0x206F), (0x2190, 0x21FF),
(0x3000, 0x303F), (0x3400, 0x9FFF), (0xFF00, 0xFFEF), (0xFFFD, 0xFFFD)]
def latin_fonts(destination, source):
"""Create row-major alpha8 banks; record = (codepoint - 32) * 240."""
assert hashlib.sha256(LATIN_FONT.read_bytes()).hexdigest() == LATIN_SHA256, "pinned Latin font source changed"
font = ImageFont.truetype(str(LATIN_FONT), 16)
cascadia, unifont, ink_bounds = bytearray(), bytearray(), {}
for code in range(ASCII_FIRST, ASCII_LAST + 1):
char = chr(code)
image = Image.new("L", (CELL_WIDTH, CELL_HEIGHT))
# U+007F is a blank control slot, never a printable glyph.
if code != ASCII_LAST:
x = int((CELL_WIDTH - font.getlength(char)) // 2)
bbox = font.getbbox(char, anchor="ls")
assert 0 <= x + bbox[0] and x + bbox[2] <= CELL_WIDTH
assert 0 <= BASELINE + bbox[1] and BASELINE + bbox[3] <= CELL_HEIGHT
ImageDraw.Draw(image).text((x, BASELINE), char, font=font, fill=255, anchor="ls")
ink_bounds[char] = list(image.getbbox()) if image.getbbox() else None
cascadia.extend(image.tobytes())
image = Image.new("L", (CELL_WIDTH, CELL_HEIGHT))
if code != ASCII_LAST:
narrow = source[code]
assert len(narrow) == 16, "ASCII must have narrow Unifont glyphs"
for y, row in enumerate(narrow):
for x in range(8):
if row & (0x80 >> x):
image.putpixel((x + 2, y + 2), 255)
unifont.extend(image.tobytes())
assert all(ink_bounds[c][3] > ink_bounds["a"][3] for c in "gjpqy")
assert any(0 < alpha < 255 for alpha in cascadia), "Latin font must retain antialias coverage"
(destination / "xfe-mono.bin").write_bytes(cascadia)
(destination / "unifont-latin.bin").write_bytes(unifont)
atlas=bytearray(); scales=[]
for percent in range(100,401,25):
width=(CELL_WIDTH*percent+50)//100
height=(CELL_HEIGHT*percent+50)//100
baseline=(BASELINE*percent+50)//100
native=ImageFont.truetype(str(LATIN_FONT),(16*percent+50)//100)
start=len(atlas)
for code in range(ASCII_FIRST,ASCII_LAST+1):
image=Image.new('L',(width,height))
if code!=ASCII_LAST:
x=int((width-native.getlength(chr(code)))//2)
ImageDraw.Draw(image).text((x,baseline),chr(code),font=native,fill=255,anchor='ls')
atlas.extend(image.tobytes())
scales.append({'percent':percent,'offset':start,'cell_width':width,'cell_height':height,
'baseline_y':baseline,'pixel_size':(16*percent+50)//100})
assert bytes(atlas[:len(cascadia)])==bytes(cascadia)
(destination/'xfe-mono-scales.bin').write_bytes(atlas)
# Scale descriptors live in fonts.json and are language-independent.
fonts = {
"format": "XFE alpha8 Latin font bank v1", "default_font_id": 0,
"codepoint_first": ASCII_FIRST, "codepoint_last": ASCII_LAST,
"cell_width": CELL_WIDTH, "cell_height": CELL_HEIGHT,
"record_bytes": CELL_WIDTH * CELL_HEIGHT, "pixel_format": "alpha8-row-major",
"baseline_y": BASELINE,
"fonts": [
{"id": 0, "name": "XFE Mono", "file": "xfe-mono.bin",
"derived_from": "Cascadia Mono 2407.24; ASCII subset and bitmap format conversion",
"copyright": "Copyright (c) 2019 - Present, Microsoft Corporation",
"pixel_size": 16, "font_advance": font.getlength("x"),
"ascent": font.getmetrics()[0], "descent": font.getmetrics()[1],
"ink_bounds": ink_bounds, "sha256": hashlib.sha256(cascadia).hexdigest(),
"source": "assets/fonts/cascadia-mono-2407.24/CascadiaMono-Regular.ttf",
"source_sha256": LATIN_SHA256, "version": "2407.24", "license": "OFL-1.1",
"license_file": "assets/fonts/cascadia-mono-2407.24/OFL-1.1.txt",
"source_url": "https://github.com/microsoft/cascadia-code/releases/tag/v2407.24"},
{"id": 1, "name": "Unifont", "file": "unifont-latin.bin",
"pixel_size": 16, "sha256": hashlib.sha256(unifont).hexdigest(),
"source_sha256": FONT_SHA256, "version": "16.0.04", "license": "OFL-1.1",
"license_file": "assets/fonts/unifont-16.0.04/OFL-1.1.txt"},
],
"native_scales": {'font_id':0,'file':'xfe-mono-scales.bin','sha256':hashlib.sha256(atlas).hexdigest(),
'format':'alpha8-row-major; 96 codepoints per scale','scales':scales},
"consolas": {"available": False,
"reason": "Windows font license does not permit bitmap conversion or OS embedding; a separate license is required.",
"license_reference": "https://learn.microsoft.com/en-us/typography/fonts/font-faq"},
}
(destination / "fonts.json").write_text(json.dumps(fonts, ensure_ascii=False, indent=2)+"\n", encoding="utf-8")
return fonts
def main():
assert hashlib.sha256(FONT.read_bytes()).hexdigest() == FONT_SHA256, "pinned font source changed"
source = {}
for line in gzip.decompress(FONT.read_bytes()).decode("ascii").splitlines():
code, value = line.split(":")
source[int(code, 16)] = bytes.fromhex(value)
destination = HERE / "console"
destination.mkdir(exist_ok=True)
fonts = latin_fonts(destination, source)
icon_path = HERE / "png/xfesystem-64.png"
icon = Image.open(icon_path).convert("RGBA")
assert icon.size == (64, 64)
(destination / "icon.rgba").write_bytes(icon.tobytes())
bank, ranges = bytearray(), []
for start, end in RANGES:
ranges.append({"start": start, "end": end, "byte_offset": len(bank)})
for code in range(start, end + 1):
value = source[code]
width = len(value) // 2
assert width in (8, 16)
bank.append(width)
# One big-endian u16 per row; narrow glyphs use the high 8 bits.
bank.extend(value if width == 16 else b"".join(bytes((b, 0)) for b in value))
(destination / "xfe-unicode.bin").write_bytes(bank)
metadata = {
"format": "XFE console assets v1", "cell_width": 12, "cell_height": 20,
"content_top": 104, "footer_height": 32, "bottom_gap": 12,
"margin": {"width_at_least_800": 24, "smaller": 16},
"foreground_rgb": [117, 201, 245], "background_rgb": [9, 18, 27],
"ascii": {"file": "xfe-mono.bin", "format": "alpha8-row-major",
"offset_x": 0, "offset_y": 0, "scale": 1, "bitmap_width": CELL_WIDTH,
"bitmap_height": CELL_HEIGHT, "codepoint_first": ASCII_FIRST,
"codepoint_last": ASCII_LAST, "record_bytes": CELL_WIDTH * CELL_HEIGHT,
"baseline_y": BASELINE},
"fonts": {"metadata": "fonts.json", "default_font_id": fonts["default_font_id"]},
"unicode": {"file": "xfe-unicode.bin", "record_bytes": 33,
"width_byte": 0, "bitmap_offset": 1, "rows": 16,
"row_byte_order": "big", "narrow_offset_x": 2, "wide_offset_x": 4,
"offset_y": 2, "ranges": ranges, "glyph_count": len(bank)//33,
"sha256": hashlib.sha256(bank).hexdigest()},
"terminal_wide_ranges": [[0x1100, 0x115F], [0x2329, 0x232A], [0x2E80, 0xA4CF],
[0xAC00, 0xD7A3], [0xF900, 0xFAFF], [0xFE10, 0xFE19], [0xFE30, 0xFE6F],
[0xFF00, 0xFF60], [0xFFE0, 0xFFE6], [0x1F300, 0x1FAFF], [0x20000, 0x3FFFD]],
"icon": {"file": "icon.rgba", "width": 64, "height": 64, "position_y": 16,
"source": "assets/branding/png/xfesystem-64.png",
"source_sha256": hashlib.sha256(icon_path.read_bytes()).hexdigest(),
"sha256": hashlib.sha256(icon.tobytes()).hexdigest()},
"font_source": {"version": "GNU Unifont 16.0.04", "license": "OFL-1.1",
"sha256": FONT_SHA256,
"url": "https://unifoundry.com/pub/unifont/unifont-16.0.04/font-builds/unifont-16.0.04.hex.gz"},
}
(destination / "metadata.json").write_text(json.dumps(metadata, ensure_ascii=False, indent=2)+"\n", encoding="utf-8")
# Unicode ranges are recorded in metadata.json for the C# renderer.
print(f"CONSOLE_ASSETS_OK: {len(bank)//33} Unicode glyphs, {len(bank)} bytes; existing 64px icon embedded")
if __name__ == "__main__":
main()
"""Rebuild checked-in console assets. Python 3 + Pillow 12.3.0; no build-time download.
Pixels are converted from the existing project icon, never redrawn. The Chinese
bitmap is an OFL-1.1 subset of the pinned GNU Unifont source beside its licenses.
The Latin fonts share a real baseline: glyphs are never individually stretched
or aligned by their bounding boxes. Descenders retain their rows below it.
"""
from __future__ import annotations
import gzip, hashlib, json
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
ROOT = Path(__file__).resolve().parents[2]
HERE = Path(__file__).resolve().parent
FONT = ROOT / "assets/fonts/unifont-16.0.04/unifont-16.0.04.hex.gz"
FONT_SHA256 = "f9c8c7802453f47be02677176aeac2342ee96d354fad7a26cedcce48e68e1d9f"
LATIN_FONT = ROOT / "assets/fonts/cascadia-mono-2407.24/CascadiaMono-Regular.ttf"
LATIN_SHA256 = "06520d032ec274fa5040b22c6f4a1d829081b24ba40b2da56dae89bf10c7b481"
CELL_WIDTH, CELL_HEIGHT, BASELINE = 12, 20, 16
ASCII_FIRST, ASCII_LAST = 32, 127
RANGES = [(0x80, 0x7FF), (0x2000, 0x206F), (0x2190, 0x21FF),
(0x3000, 0x303F), (0x3400, 0x9FFF), (0xFF00, 0xFFEF), (0xFFFD, 0xFFFD)]
def latin_fonts(destination, source):
"""Create row-major alpha8 banks; record = (codepoint - 32) * 240."""
assert hashlib.sha256(LATIN_FONT.read_bytes()).hexdigest() == LATIN_SHA256, "pinned Latin font source changed"
font = ImageFont.truetype(str(LATIN_FONT), 16)
cascadia, unifont, ink_bounds = bytearray(), bytearray(), {}
for code in range(ASCII_FIRST, ASCII_LAST + 1):
char = chr(code)
image = Image.new("L", (CELL_WIDTH, CELL_HEIGHT))
# U+007F is a blank control slot, never a printable glyph.
if code != ASCII_LAST:
x = int((CELL_WIDTH - font.getlength(char)) // 2)
bbox = font.getbbox(char, anchor="ls")
assert 0 <= x + bbox[0] and x + bbox[2] <= CELL_WIDTH
assert 0 <= BASELINE + bbox[1] and BASELINE + bbox[3] <= CELL_HEIGHT
ImageDraw.Draw(image).text((x, BASELINE), char, font=font, fill=255, anchor="ls")
ink_bounds[char] = list(image.getbbox()) if image.getbbox() else None
cascadia.extend(image.tobytes())
image = Image.new("L", (CELL_WIDTH, CELL_HEIGHT))
if code != ASCII_LAST:
narrow = source[code]
assert len(narrow) == 16, "ASCII must have narrow Unifont glyphs"
for y, row in enumerate(narrow):
for x in range(8):
if row & (0x80 >> x):
image.putpixel((x + 2, y + 2), 255)
unifont.extend(image.tobytes())
assert all(ink_bounds[c][3] > ink_bounds["a"][3] for c in "gjpqy")
assert any(0 < alpha < 255 for alpha in cascadia), "Latin font must retain antialias coverage"
(destination / "xfe-mono.bin").write_bytes(cascadia)
(destination / "unifont-latin.bin").write_bytes(unifont)
atlas=bytearray(); scales=[]
for percent in range(100,401,25):
width=(CELL_WIDTH*percent+50)//100
height=(CELL_HEIGHT*percent+50)//100
baseline=(BASELINE*percent+50)//100
native=ImageFont.truetype(str(LATIN_FONT),(16*percent+50)//100)
start=len(atlas)
for code in range(ASCII_FIRST,ASCII_LAST+1):
image=Image.new('L',(width,height))
if code!=ASCII_LAST:
x=int((width-native.getlength(chr(code)))//2)
ImageDraw.Draw(image).text((x,baseline),chr(code),font=native,fill=255,anchor='ls')
atlas.extend(image.tobytes())
scales.append({'percent':percent,'offset':start,'cell_width':width,'cell_height':height,
'baseline_y':baseline,'pixel_size':(16*percent+50)//100})
assert bytes(atlas[:len(cascadia)])==bytes(cascadia)
(destination/'xfe-mono-scales.bin').write_bytes(atlas)
# Scale descriptors live in fonts.json and are language-independent.
fonts = {
"format": "XFE alpha8 Latin font bank v1", "default_font_id": 0,
"codepoint_first": ASCII_FIRST, "codepoint_last": ASCII_LAST,
"cell_width": CELL_WIDTH, "cell_height": CELL_HEIGHT,
"record_bytes": CELL_WIDTH * CELL_HEIGHT, "pixel_format": "alpha8-row-major",
"baseline_y": BASELINE,
"fonts": [
{"id": 0, "name": "XFE Mono", "file": "xfe-mono.bin",
"derived_from": "Cascadia Mono 2407.24; ASCII subset and bitmap format conversion",
"copyright": "Copyright (c) 2019 - Present, Microsoft Corporation",
"pixel_size": 16, "font_advance": font.getlength("x"),
"ascent": font.getmetrics()[0], "descent": font.getmetrics()[1],
"ink_bounds": ink_bounds, "sha256": hashlib.sha256(cascadia).hexdigest(),
"source": "assets/fonts/cascadia-mono-2407.24/CascadiaMono-Regular.ttf",
"source_sha256": LATIN_SHA256, "version": "2407.24", "license": "OFL-1.1",
"license_file": "assets/fonts/cascadia-mono-2407.24/OFL-1.1.txt",
"source_url": "https://github.com/microsoft/cascadia-code/releases/tag/v2407.24"},
{"id": 1, "name": "Unifont", "file": "unifont-latin.bin",
"pixel_size": 16, "sha256": hashlib.sha256(unifont).hexdigest(),
"source_sha256": FONT_SHA256, "version": "16.0.04", "license": "OFL-1.1",
"license_file": "assets/fonts/unifont-16.0.04/OFL-1.1.txt"},
],
"native_scales": {'font_id':0,'file':'xfe-mono-scales.bin','sha256':hashlib.sha256(atlas).hexdigest(),
'format':'alpha8-row-major; 96 codepoints per scale','scales':scales},
"consolas": {"available": False,
"reason": "Windows font license does not permit bitmap conversion or OS embedding; a separate license is required.",
"license_reference": "https://learn.microsoft.com/en-us/typography/fonts/font-faq"},
}
(destination / "fonts.json").write_text(json.dumps(fonts, ensure_ascii=False, indent=2)+"\n", encoding="utf-8")
return fonts
def main():
assert hashlib.sha256(FONT.read_bytes()).hexdigest() == FONT_SHA256, "pinned font source changed"
source = {}
for line in gzip.decompress(FONT.read_bytes()).decode("ascii").splitlines():
code, value = line.split(":")
source[int(code, 16)] = bytes.fromhex(value)
destination = HERE / "console"
destination.mkdir(exist_ok=True)
fonts = latin_fonts(destination, source)
icon_path = HERE / "png/xfesystem-64.png"
icon = Image.open(icon_path).convert("RGBA")
assert icon.size == (64, 64)
(destination / "icon.rgba").write_bytes(icon.tobytes())
bank, ranges = bytearray(), []
for start, end in RANGES:
ranges.append({"start": start, "end": end, "byte_offset": len(bank)})
for code in range(start, end + 1):
value = source[code]
width = len(value) // 2
assert width in (8, 16)
bank.append(width)
# One big-endian u16 per row; narrow glyphs use the high 8 bits.
bank.extend(value if width == 16 else b"".join(bytes((b, 0)) for b in value))
(destination / "xfe-unicode.bin").write_bytes(bank)
metadata = {
"format": "XFE console assets v1", "cell_width": 12, "cell_height": 20,
"content_top": 104, "footer_height": 32, "bottom_gap": 12,
"margin": {"width_at_least_800": 24, "smaller": 16},
"foreground_rgb": [117, 201, 245], "background_rgb": [9, 18, 27],
"ascii": {"file": "xfe-mono.bin", "format": "alpha8-row-major",
"offset_x": 0, "offset_y": 0, "scale": 1, "bitmap_width": CELL_WIDTH,
"bitmap_height": CELL_HEIGHT, "codepoint_first": ASCII_FIRST,
"codepoint_last": ASCII_LAST, "record_bytes": CELL_WIDTH * CELL_HEIGHT,
"baseline_y": BASELINE},
"fonts": {"metadata": "fonts.json", "default_font_id": fonts["default_font_id"]},
"unicode": {"file": "xfe-unicode.bin", "record_bytes": 33,
"width_byte": 0, "bitmap_offset": 1, "rows": 16,
"row_byte_order": "big", "narrow_offset_x": 2, "wide_offset_x": 4,
"offset_y": 2, "ranges": ranges, "glyph_count": len(bank)//33,
"sha256": hashlib.sha256(bank).hexdigest()},
"terminal_wide_ranges": [[0x1100, 0x115F], [0x2329, 0x232A], [0x2E80, 0xA4CF],
[0xAC00, 0xD7A3], [0xF900, 0xFAFF], [0xFE10, 0xFE19], [0xFE30, 0xFE6F],
[0xFF00, 0xFF60], [0xFFE0, 0xFFE6], [0x1F300, 0x1FAFF], [0x20000, 0x3FFFD]],
"icon": {"file": "icon.rgba", "width": 64, "height": 64, "position_y": 16,
"source": "assets/branding/png/xfesystem-64.png",
"source_sha256": hashlib.sha256(icon_path.read_bytes()).hexdigest(),
"sha256": hashlib.sha256(icon.tobytes()).hexdigest()},
"font_source": {"version": "GNU Unifont 16.0.04", "license": "OFL-1.1",
"sha256": FONT_SHA256,
"url": "https://unifoundry.com/pub/unifont/unifont-16.0.04/font-builds/unifont-16.0.04.hex.gz"},
}
(destination / "metadata.json").write_text(json.dumps(metadata, ensure_ascii=False, indent=2)+"\n", encoding="utf-8")
# Unicode ranges are recorded in metadata.json for the C# renderer.
print(f"CONSOLE_ASSETS_OK: {len(bank)//33} Unicode glyphs, {len(bank)} bytes; existing 64px icon embedded")
if __name__ == "__main__":
main()