Spaces:
Sleeping
Sleeping
Download svg_builder.py from wisamWR/ditherforge-api: direct link, hf CLI and curl.
- Browser
- Download file 3.1 kB
-
https://huggingface.co/spaces/wisamWR/ditherforge-api/resolve/main/svg_builder.py
- Command line
-
hf download hf://spaces/wisamWR/ditherforge-api/svg_builder.py
-
curl -L -o svg_builder.py https://huggingface.co/spaces/wisamWR/ditherforge-api/resolve/main/svg_builder.py
3.1 kB
| """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 "" | |