Spaces:
Sleeping
Sleeping
File size: 3,096 Bytes
ffe3e3c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | """SVG generation with optional CSS animations."""
from __future__ import annotations
from html import escape
def text_to_svg(
text: str,
font_size: int = 14,
color: str = "#00ff41",
bg_color: str = "#0d1117",
animation: str = "none",
duration: float = 2.0,
) -> str:
lines = text.split("\n")
cols = max((len(line) for line in lines), default=0)
char_w = font_size * 0.6
line_h = font_size * 1.2
svg_w = int(cols * char_w)
svg_h = int(len(lines) * line_h)
parts = [
f'<svg xmlns="http://www.w3.org/2000/svg" width="{svg_w}" height="{svg_h}" '
f'viewBox="0 0 {svg_w} {svg_h}">',
f'<rect width="100%" height="100%" fill="{bg_color}"/>',
]
css = _animation_css(animation, duration, len(lines))
if css:
parts.append(f"<style>{css}</style>")
use_row_class = animation in ("reveal", "matrix")
parts.append(
f'<text font-family="monospace" font-size="{font_size}" fill="{color}">'
)
for i, line in enumerate(lines):
y = (i + 1) * line_h - (line_h - font_size) / 2
cls = f' class="row row-{i}"' if use_row_class else ""
parts.append(f'<tspan x="0" y="{y:.1f}"{cls}>{escape(line)}</tspan>')
parts.append("</text>")
if animation == "scanline":
parts.append(
f'<rect class="scanline" x="0" y="0" width="100%" '
f'height="{line_h}" fill="{color}" opacity="0.15"/>'
)
parts.append("</svg>")
return "".join(parts)
def _animation_css(animation: str, duration: float, rows: int) -> str:
if animation == "reveal":
delays = "\n".join(
f".row-{i} {{ animation-delay: {i * duration / rows:.2f}s; }}"
for i in range(rows)
)
return (
".row { opacity: 0; animation: reveal 0.15s ease-out forwards; }\n"
"@keyframes reveal { from { opacity: 0; } to { opacity: 1; } }\n"
+ delays
)
if animation == "scanline":
return (
f"@keyframes scan {{ 0% {{ transform: translateY(-5%); }} "
f"100% {{ transform: translateY(105%); }} }}\n"
f".scanline {{ animation: scan {duration}s linear infinite; }}"
)
if animation == "flicker":
return (
"@keyframes flicker { 0%,100% { opacity:1; } "
"92% { opacity:1; } 93% { opacity:0.3; } "
"94% { opacity:1; } 96% { opacity:0.5; } 97% { opacity:1; } }\n"
f"text {{ animation: flicker {duration}s ease-in-out infinite; }}"
)
if animation == "matrix":
delays = "\n".join(
f".row-{i} {{ animation-delay: {i * duration / rows:.2f}s; }}"
for i in range(rows)
)
return (
f".row {{ opacity: 0; animation: matrix {duration * 0.3:.2f}s ease-out forwards; }}\n"
"@keyframes matrix { "
"0% { opacity:0; transform:translateY(-20px); } "
"60% { opacity:1; } "
"100% { opacity:1; transform:translateY(0); } }\n"
+ delays
)
return ""
|