#!/usr/bin/env nu # SVG optimization utilities # Optimize SVG content (basic optimization without external tools) export def optimize_svg_content [svg_content: string, config: record] { let optimized = ($svg_content # Remove XML comments | str replace -ar '' '' # Remove unnecessary whitespace between tags | str replace -ar '>\s+<' '><' # Remove newlines and extra spaces | str replace -ar '\n\s*' '' | str replace -ar '\s+' ' ' # Remove unnecessary attributes (if safe) | str replace -ar '\s+xmlns:xlink="[^"]*"' '' # Round numbers to reduce precision (basic) ) let rounded = (round_svg_numbers $optimized $config.svg_precision) let final = ($rounded | str trim) $final } # Round SVG numeric values to reduce file size def round_svg_numbers [content: string, precision: int] { # Basic SVG number optimization using simple regex # Round decimal numbers to specified precision $content | str replace -ar '(\d+\.\d{4,})' '$1' # Keep numbers as-is for now | str replace -ar '\s+' ' ' # Remove extra whitespace | str trim } # Convert SVG to data URI export def svg_to_data_uri [svg_content: string] { let cleaned = ($svg_content # Remove newlines and extra spaces | str replace -ar '\n\s*' ' ' | str replace -ar '\s+' ' ' # URL encode special characters | str replace -ar '"' '%22' | str replace -ar '<' '%3C' | str replace -ar '>' '%3E' | str replace -ar '#' '%23' | str trim ) $"data:image/svg+xml,%3Csvg ($cleaned | str substring 4..)" } # Extract symbols from SVG sprite export def extract_svg_symbols [svg_content: string] { # Extract symbol definitions using regex parsing let symbol_matches = ($svg_content | parse -r ']*id="([^"]*)"[^>]*(?:viewBox="([^"]*)")?[^>]*>(.*?)') $symbol_matches | each { |match| { id: $match.capture0, content: $match.capture2, viewBox: (if ($match.capture1 | is-empty) { "0 0 24 24" } else { $match.capture1 }) } } } # Create optimized SVG sprite export def create_svg_sprite [symbols: list, config: record] { let sprite_content = ($symbols | each { |symbol| $'($symbol.content)' } | str join "\n" ) let full_sprite = $' ($sprite_content) ' if $config.svg_optimize { optimize_svg_content $full_sprite $config } else { $full_sprite } } # Analyze SVG for optimization opportunities export def analyze_svg [svg_content: string] { let size = ($svg_content | str length) let lines = ($svg_content | lines | length) let path_elements = ($svg_content | str replace -ar ']*>' '' | str length) let path_count = (($size - $path_elements) / 50) # Rough estimate based on size difference { size_bytes: $size, estimated_elements: $lines, estimated_paths: $path_count, has_animations: ($svg_content | str contains "animate"), has_gradients: ($svg_content | str contains "gradient"), has_filters: ($svg_content | str contains "filter"), compression_potential: (($size * 0.4) | math round) } } # Split SVG into critical and non-critical based on usage export def categorize_svg_symbols [svg_content: string] { let all_symbols = (extract_svg_symbols $svg_content) # Define critical symbols (above-the-fold, animated) let critical_ids = ["logo-black", "logo-text-black"] let critical_symbols = ($all_symbols | where $it.id in $critical_ids) let decorative_symbols = ($all_symbols | where $it.id not-in $critical_ids) { critical: $critical_symbols, decorative: $decorative_symbols } } # Generate CSS classes for SVG symbols export def generate_svg_css_classes [symbols: list] { # Generate CSS classes for each symbol $symbols | each { |symbol| let class_name = ($symbol.id | str replace -ar '[^a-zA-Z0-9]' '-') let svg_data = $"($symbol.content)" let encoded = ($svg_data | str replace '"' "'" | str replace '#' '%23') $".icon-($class_name) { background-image: url('data:image/svg+xml;utf8,($encoded)'); }" } | str join "\n" } # Optimize SVG animations for performance export def optimize_svg_animations [svg_content: string] { # Basic optimization - remove unnecessary animation attributes let optimized = ($svg_content # Remove animation debugging attributes | str replace -ar '\s+calcMode="[^"]*"' '' | str replace -ar '\s+keyTimes="[^"]*"' '' # Simplify animation values where possible | str replace -ar 'repeatCount="indefinite"' 'repeatCount="indefinite"' ) $optimized } # Create fallback PNG data for SVG (requires external tool) export def create_svg_fallback [svg_content: string, width: int = 24, height: int = 24] { # This would require an external tool like Inkscape or rsvg-convert # For now, return a placeholder "# SVG fallback generation requires external tools (rsvg-convert, inkscape)" } # Validate SVG syntax export def validate_svg [svg_content: string] { let has_svg_tag = ($svg_content | str contains "") let balanced_tags = true # This would need proper XML parsing { valid: ($has_svg_tag and $has_closing_tag and $balanced_tags), has_svg_root: $has_svg_tag, has_closing_tag: $has_closing_tag, estimated_valid: $balanced_tags } } # Generate SVG performance report export def svg_performance_report [svg_files: list] { $svg_files | each { |file| if ($file | path exists) { let content = (open $file) let analysis = (analyze_svg $content) let validation = (validate_svg $content) { file: $file, size: $analysis.size_bytes, elements: $analysis.estimated_elements, paths: $analysis.estimated_paths, animations: $analysis.has_animations, valid: $validation.valid, optimization_potential: $analysis.compression_potential } } } }