8b501266ff
Add live agent progress, elapsed timing, verified code navigation and rich Markdown rendering. Preserve NanumSquare styling with missing glyph fallback, fix platform availability filtering and unify workspace scrollbar and visualization controls.
86 lines
3.1 KiB
Python
86 lines
3.1 KiB
Python
"""Regenerate font coverage after replacing the bundled NanumSquare WOFF2 files.
|
|
|
|
Requires fonttools[woff]. Normal app builds use the checked-in CSS, not Python.
|
|
Run: python3 scripts/generate-nanumsquare-css.py [--check]
|
|
"""
|
|
|
|
import argparse
|
|
import hashlib
|
|
import unicodedata
|
|
from pathlib import Path
|
|
|
|
from fontTools.pens.boundsPen import BoundsPen
|
|
from fontTools.ttLib import TTFont
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
FONTS = ROOT / "src/renderer/public/assets/fonts"
|
|
OUTPUT = ROOT / "src/renderer/src/styles/nanumSquare.css"
|
|
FACES = [(300, "NanumSquareL"), (400, "NanumSquare"), (700, "NanumSquareB"), (800, "NanumSquareEB")]
|
|
# Hangul fillers are intentionally invisible, like whitespace and controls.
|
|
FILLERS = {0x115F, 0x1160, 0x3164, 0xFFA0}
|
|
|
|
|
|
def coverage(font):
|
|
glyphs = font.getGlyphSet()
|
|
supported = []
|
|
for code, name in sorted(font.getBestCmap().items()):
|
|
pen = BoundsPen(glyphs)
|
|
glyphs[name].draw(pen)
|
|
intentional = chr(code).isspace() or unicodedata.category(chr(code)) in {"Cc", "Cf"} or code in FILLERS
|
|
if pen.bounds is not None or intentional:
|
|
supported.append(code)
|
|
return supported
|
|
|
|
|
|
def unicode_ranges(codes):
|
|
ranges = []
|
|
start = end = codes[0]
|
|
for code in codes[1:]:
|
|
if code == end + 1:
|
|
end = code
|
|
else:
|
|
ranges.append((start, end))
|
|
start = end = code
|
|
ranges.append((start, end))
|
|
return [f"U+{start:X}" if start == end else f"U+{start:X}-{end:X}" for start, end in ranges]
|
|
|
|
|
|
def generate():
|
|
sections = [
|
|
"/* Generated by scripts/generate-nanumsquare-css.py. Do not edit manually.\n"
|
|
" * Empty glyphs are excluded so the existing system-font fallback can render them.\n"
|
|
" * Font binaries and visible glyphs are unchanged.\n */"
|
|
]
|
|
for weight, name in FACES:
|
|
file = FONTS / f"{name}.woff2"
|
|
with TTFont(file) as font:
|
|
ranges = unicode_ranges(coverage(font))
|
|
lines = [", ".join(ranges[index:index + 10]) for index in range(0, len(ranges), 10)]
|
|
sections.append(
|
|
f"/* {file.name} sha256: {hashlib.sha256(file.read_bytes()).hexdigest()} */\n"
|
|
"@font-face {\n"
|
|
" font-family: 'NanumSquare';\n"
|
|
f" src: url('/assets/fonts/{file.name}') format('woff2');\n"
|
|
f" font-weight: {weight};\n"
|
|
" font-style: normal;\n"
|
|
" font-display: swap;\n"
|
|
" unicode-range:\n " + ",\n ".join(lines) + ";\n}"
|
|
)
|
|
return "\n\n".join(sections) + "\n"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--check", action="store_true")
|
|
args = parser.parse_args()
|
|
generated = generate()
|
|
if args.check:
|
|
if not OUTPUT.exists() or OUTPUT.read_text(encoding="utf-8") != generated:
|
|
raise SystemExit("NanumSquare coverage CSS is stale; regenerate it")
|
|
print("NanumSquare coverage matches all four font files")
|
|
else:
|
|
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
|
|
OUTPUT.write_text(generated, encoding="utf-8")
|
|
print(f"Generated {OUTPUT.relative_to(ROOT)}")
|