provisioning-outreach/presentations/rust-laspalmas-250926/auroraframe/framework/optimizers/optimize-svg.nu

184 lines
6.4 KiB
Text
Raw Normal View History

#!/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 '<!--[\s\S]*?-->' ''
# 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 '<symbol[^>]*id="([^"]*)"[^>]*(?:viewBox="([^"]*)")?[^>]*>(.*?)</symbol>')
$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<record>, config: record] {
let sprite_content = ($symbols
| each { |symbol|
$'<symbol id="($symbol.id)" viewBox="($symbol.viewBox // "0 0 24 24")">($symbol.content)</symbol>'
}
| str join "\n"
)
let full_sprite = $'<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
($sprite_content)
</svg>'
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 '<path[^>]*>' '' | 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<record>] {
# Generate CSS classes for each symbol
$symbols | each { |symbol|
let class_name = ($symbol.id | str replace -ar '[^a-zA-Z0-9]' '-')
let svg_data = $"<svg viewBox=\"($symbol.viewBox)\">($symbol.content)</svg>"
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 "<svg")
let has_closing_tag = ($svg_content | str contains "</svg>")
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<string>] {
$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
}
}
}
}