ArthurZ's picture
ArthurZ HF Staff
Add arch_svg generator source (meta-device introspection + modular diff + SVG render)
a9cfb03 verified
Raw
History Blame Contribute Delete
2.36 kB
"""Tiny self-contained layout primitives β€” our own engine, no external deps.
The point of this module is **text-aware sizing**: every other part of the package can ask
"how wide is this string at this font size?" and "make this string fit in this width", so
boxes are sized to their content and text never overflows or runs together. Kept dependency-
free and deterministic (a fixed proportional-width table) so output stays reproducible.
"""
from __future__ import annotations
# per-character width as a fraction of the font size, for a generic sans-serif.
# Approximate but stable; good enough to size/ellipsize boxes so text fits.
_NARROW = set("iIl.,:;'!|()[]{}ftj ")
_WIDE = set("mMW@%β€”β€“β†’βŠ™βŠ•")
_MID = set("rtszc")
def char_em(ch: str) -> float:
if ch in _NARROW:
return 0.30
if ch in _WIDE:
return 0.92
if ch in _MID:
return 0.48
if ch.isupper() or ch.isdigit():
return 0.62
return 0.54
def text_width(s: str, px: float) -> float:
"""Estimated rendered width (in px) of ``s`` at font size ``px``."""
return sum(char_em(c) for c in s) * px
def fit_text(s: str, max_px: float, px: float) -> str:
"""Return ``s`` unchanged if it fits in ``max_px``, else truncated with a trailing ellipsis."""
if max_px <= 0 or text_width(s, px) <= max_px:
return s
ell = "…"
budget = max_px - text_width(ell, px)
out = []
w = 0.0
for c in s:
cw = char_em(c) * px
if w + cw > budget:
break
out.append(c)
w += cw
return ("".join(out).rstrip() + ell) if out else ell
def wrap_text(s: str, max_px: float, px: float, max_lines: int = 2) -> list[str]:
"""Greedily wrap ``s`` to ``max_px`` over up to ``max_lines`` lines (last line ellipsized)."""
if text_width(s, px) <= max_px:
return [s]
words = s.split(" ")
lines: list[str] = []
cur = ""
for w in words:
trial = (cur + " " + w).strip()
if text_width(trial, px) <= max_px or not cur:
cur = trial
else:
lines.append(cur)
cur = w
if len(lines) == max_lines - 1:
break
rest = " ".join(words[sum(len(line.split(" ")) for line in lines) :]) if lines else cur
lines.append(fit_text(rest or cur, max_px, px))
return lines[:max_lines]