"""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'', f'', ] css = _animation_css(animation, duration, len(lines)) if css: parts.append(f"") use_row_class = animation in ("reveal", "matrix") parts.append( f'' ) 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'{escape(line)}') parts.append("") if animation == "scanline": parts.append( f'' ) parts.append("") 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 ""