XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
"""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()