Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# MuGlyph
<p align="center">
<img src="docs/logo.svg" alt="MuGlyph logo" width="120" height="120">
</p>

A hyper-modern, cross-platform **TUI client for MU\*** (MUSH / MUCK / MUD) worlds, built for
GPU-accelerated terminals (Kitty, WezTerm, Ghostty) on **Windows and Linux**.
<h1 align="center">MuGlyph</h1>

<p align="center">A hyper-modern, cross-platform <strong>TUI client for MU*</strong> (MUSH / MUCK / MUD) worlds, built for GPU-accelerated terminals (Kitty, WezTerm, Ghostty) on <strong>Windows and Linux</strong>.</p>

The goal is [BeipMU](https://beipdev.github.io/BeipMU/)-class feature parity in a terminal-native
client: rich truecolor text, inline graphics, powerful automation, and full MU\* protocol support.
Expand Down
Binary file added assets/fonts/MuGlyph.ttf
Binary file not shown.
20 changes: 20 additions & 0 deletions assets/fonts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# MuGlyph icon font

`MuGlyph.ttf` is a single-glyph font containing the MuGlyph mark (from `docs/logo.svg`) at
codepoint **U+E000** (Private Use Area). It's meant to be merged into your terminal font (or a
Nerd-Font-style patch) so the logo can appear in the TUI header as a normal character.

Regenerate it from the SVG with:

```bash
pip install fonttools
python3 tools/make-glyph-font.py
```

The generator scales the 512-unit SVG viewBox to a 1000-unit em and flips the Y axis (SVG is
Y-down, fonts are Y-up). To use a different codepoint or em size, edit the constants at the top
of `tools/make-glyph-font.py`.

> Terminals render text in the user's configured font, so a TUI can't ship its own glyph — this
> font is for users who want the mark in their terminal. The header falls back to a text label
> when the glyph isn't present.
71 changes: 71 additions & 0 deletions tools/make-glyph-font.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env python3
"""Build a single-glyph font from docs/logo.svg.

Produces assets/fonts/MuGlyph.ttf containing the MuGlyph mark at U+E000 (a Private
Use Area codepoint), so it can be merged into a terminal font / Nerd Font patch and
shown in the TUI header. Run: python3 tools/make-glyph-font.py
"""
import re
from pathlib import Path

from fontTools.fontBuilder import FontBuilder
from fontTools.pens.transformPen import TransformPen
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.svgLib.path.parser import parse_path

ROOT = Path(__file__).resolve().parent.parent
SVG = ROOT / "docs" / "logo.svg"
OUT = ROOT / "assets" / "fonts" / "MuGlyph.ttf"

UPM = 1000
VIEWBOX = 512
CODEPOINT = 0xE000 # Private Use Area
GLYPH_NAME = "muglyph"


def svg_path_data(svg_text: str):
return re.findall(r'<path[^>]*\bd="([^"]+)"', svg_text)


def build():
svg_text = SVG.read_text(encoding="utf-8")
paths = svg_path_data(svg_text)
if not paths:
raise SystemExit(f"No <path d=...> found in {SVG}")

# SVG is Y-down in a 512 viewBox; fonts are Y-up. Scale to the em and flip Y:
# x' = s*x ; y' = s*(VIEWBOX - y)
s = UPM / VIEWBOX
transform = (s, 0, 0, -s, 0, s * VIEWBOX)

pen = TTGlyphPen(None)
tpen = TransformPen(pen, transform)
for d in paths:
parse_path(d, tpen)
glyph = pen.glyph()

glyph_order = [".notdef", GLYPH_NAME]
fb = FontBuilder(UPM, isTTF=True)
fb.setupGlyphOrder(glyph_order)
fb.setupCharacterMap({CODEPOINT: GLYPH_NAME})

notdef = TTGlyphPen(None).glyph()
fb.setupGlyf({".notdef": notdef, GLYPH_NAME: glyph})
fb.setupHorizontalMetrics({".notdef": (UPM, 0), GLYPH_NAME: (UPM, 0)})
fb.setupHorizontalHeader(ascent=UPM, descent=0)
fb.setupNameTable({
"familyName": "MuGlyph",
"styleName": "Regular",
"psName": "MuGlyph-Regular",
"version": "1.0",
})
fb.setupOS2(sTypoAscender=UPM, sTypoDescender=0, usWinAscent=UPM, usWinDescent=0)
fb.setupPost()

OUT.parent.mkdir(parents=True, exist_ok=True)
fb.save(str(OUT))
print(f"Wrote {OUT.relative_to(ROOT)} — {len(paths)} contours at U+{CODEPOINT:04X}")


if __name__ == "__main__":
build()
Loading