termcinema-cli 0.1.0

🎬 Animated terminal-to-SVG renderer CLI for the termcinema project
Documentation
use crate::args::CliArgs;
use crate::style::{parse_speed, speed_to_delay_ms, warn_about_unrecognized_font};
use termcinema_engine::{
    ControlSpec, CursorSpec, StyleSpec, list_builtin_system_fonts, resolve_embedded_font,
};

/// Patch theme configuration using CLI arguments.
///
/// This function updates all theme-related specs in-place:
///
/// - [`patch_style`]      → applies font, color, and cursor overrides;
/// - [`patch_control`]    → applies animation timing and speed settings.
///
/// Should be called after loading a theme preset, to allow dynamic overrides via CLI.
/// Supports both interactive and script modes (via `is_script`).
pub(crate) fn patch_theme(
    style: &mut StyleSpec,
    cursor: &mut CursorSpec,
    control: &mut ControlSpec,
    args: &CliArgs,
    is_script: bool,
) {
    patch_style(style, cursor, args);
    patch_control(control, args, is_script);
}

/// Patch style and cursor configuration from CLI arguments.
///
/// This function updates both `StyleSpec` and `CursorSpec` in-place:
///
/// - `--cursor-char`        → sets the cursor character;
/// - `--font-size`          → overrides font size;
/// - `--font-family`        → applies the specified font and warns if unknown;
/// - `--text-color`         → supports hex color or `"none"` for transparent text;
/// - `--background-color`   → supports hex color or `"none"` for transparent background.
///
/// Font names are validated against embedded and system fonts.
/// Emits a warning if the font is not recognized.
fn patch_style(style: &mut StyleSpec, cursor: &mut CursorSpec, args: &CliArgs) {
    // Override cursor character
    if let Some(ch) = &args.cursor_char {
        cursor.char = ch.clone();
    }

    // Override font size
    if let Some(fs) = args.font_size {
        style.font_size = fs;
    }

    // Override font family and validate
    if let Some(ff) = &args.font_family {
        style.font_family = ff.clone();

        let is_builtin = list_builtin_system_fonts()
            .iter()
            .any(|(name, _)| name == ff);
        let is_embeddable = resolve_embedded_font(ff).is_some();

        if !is_embeddable && !is_builtin {
            warn_about_unrecognized_font(ff);
        }
    }

    // Override text color (supports "none"/"null" as transparent)
    if let Some(tc) = &args.text_color {
        style.text_color = match tc.to_lowercase().as_str() {
            "none" | "null" => None,
            _ => Some(tc.clone()),
        };
    }

    // Override background color (supports "none"/"null" as transparent)
    if let Some(bg) = &args.background_color {
        style.background_color = match bg.to_lowercase().as_str() {
            "none" | "null" => None,
            _ => Some(bg.clone()),
        };
    }
}

/// Patch control configuration from CLI arguments and rendering context.
///
/// This function updates `ControlSpec` in-place:
///
/// - `--frame-delay`        → exact delay (ms) per character if provided;
/// - `--speed`              → fallback: maps named presets to frame delay;
/// - `--fade-duration`      → controls character fade-in time (ms);
///   if not set, default is:
///   → `50ms` in script mode;
///   → `100ms` otherwise.
///
/// This enables fine-grained animation control while applying context-aware defaults.
fn patch_control(control: &mut ControlSpec, args: &CliArgs, is_script: bool) {
    // Use explicit frame delay if provided
    if let Some(ms) = args.frame_delay {
        // User explicitly set a frame delay
        control.frame_delay = Some(ms);
    } else if let Some(speed_str) = &args.speed {
        // User passed --speed, try to parse it
        match parse_speed(speed_str) {
            Some(preset) => {
                let ms = speed_to_delay_ms(&preset);
                control.frame_delay = Some(ms);
            }
            None => {
                eprintln!(
                    "⚠️ Unknown speed preset '{}', falling back to default.",
                    speed_str
                );
            }
        }
    }

    // Fade duration: use CLI value or fallback based on mode
    if let Some(dur) = args.fade_duration {
        control.fade_duration = Some(dur);
    } else {
        control.fade_duration = Some(if is_script { 50 } else { 100 });
    }
}