render_regex 0.0.0

SVG visualization of regex DFAs.
Documentation
// src/layout/profile.rs

use serde::Deserialize;
use crate::layout::strategies::LayoutStrategy;

/// How to draw arrowheads on edges.
#[derive(Clone, Debug, Deserialize)]
pub enum ArrowStyle {
    Triangle,
    Dot,
}

/// How to distribute parallel edges (fan-out) around a node pair.
#[derive(Clone, Debug, Deserialize)]
pub enum FanoutMode {
    Symmetric,
    Offset,
    Random,
}

/// A named rendering style with layout hints and visual preferences.
#[derive(Clone, Debug, Deserialize)]
#[serde(default)]
pub struct RenderProfile {
    pub name: String,

    /// Optional SVG `<title>` content
    pub title: Option<String>,
    /// Optional SVG `<desc>` content
    pub tool: Option<String>,
    /// Show debug overlay (edge indices, bend offsets)
    pub show_debug_overlay: bool,

    /// Global on/off switch for arrows
    pub no_arrows: bool,
    /// Marker size: sets markerWidth and markerHeight
    pub arrow_size: f32,
    /// Which arrowhead shape to use
    pub arrow_style: ArrowStyle,

    /// Radius of self-loop arcs
    pub loop_radius: f32,
    /// Starting angle for self-loop placement (degrees)
    pub loop_angle: f32,

    /// Maximum bend offset for parallel edges
    pub fanout_max_bend: f32,
    /// Distribution strategy for parallel edges
    pub fanout_mode: FanoutMode,
    /// Disable fan-out entirely (all edges straight)
    pub no_fanout: bool,

    /// Spacing parameter for layout
    pub spacing: f32,
    /// Default node-label font size
    pub font_size: f32,

    /// Layout strategy algorithm
    pub layout: LayoutStrategy,
    /// Order in which SVG layers are rendered
    pub draw_order: Vec<String>,
    /// Stroke width applied to both node borders and edge lines
    pub stroke_width: f32,
}

impl Default for RenderProfile {
    fn default() -> Self {
        Self {
            name: "default".into(),
            title: None,
            tool: None,
            show_debug_overlay: false,

            no_arrows: false,
            arrow_size: 6.0,
            arrow_style: ArrowStyle::Triangle,

            loop_radius: 10.0,
            loop_angle: 45.0,

            fanout_max_bend: 40.0,
            fanout_mode: FanoutMode::Symmetric,
            no_fanout: false,

            spacing: 80.0,
            font_size: 14.0,

            layout: LayoutStrategy::TreeTopDown,
            draw_order: vec!["edges".into(), "nodes".into()],
	    stroke_width: 1.0,
        }
    }
}

impl RenderProfile {
    /// Built-in presets
    pub fn preset(name: &str) -> Self {
        match name {
            "compact" | "default" => Self {
                name: "compact".into(),
                title: None,
                tool: None,
                show_debug_overlay: false,

                no_arrows: false,
                arrow_size: 6.0,
                arrow_style: ArrowStyle::Triangle,

                loop_radius: 10.0,
                loop_angle: 45.0,

                fanout_max_bend: 40.0,
                fanout_mode: FanoutMode::Symmetric,
                no_fanout: false,

                spacing: 80.0,
                font_size: 10.0,
		stroke_width: 1.0,

                layout: LayoutStrategy::TreeTopDown,
                draw_order: vec!["edges".into(), "nodes".into()],
            },
            "debug" => Self {
                name: "debug".into(),
                title: None,
                tool: None,
                show_debug_overlay: true,

                no_arrows: false,
                arrow_size: 6.0,
                arrow_style: ArrowStyle::Triangle,

                loop_radius: 10.0,
                loop_angle: 45.0,

                fanout_max_bend: 40.0,
                fanout_mode: FanoutMode::Symmetric,
                no_fanout: false,

                spacing: 80.0,
                font_size: 12.0,
		stroke_width: 1.0,

                layout: LayoutStrategy::TreeTopDown,
                draw_order: vec!["edges".into(), "nodes".into()],
            },
            _ => Self::default(),
        }
    }
}

/// Represents a user-loaded `.cf` configuration (all fields optional).
#[derive(Clone, Debug, Deserialize, Default)]
#[serde(default)]
pub struct ProfileConfig {
    pub title: Option<String>,
    pub show_debug_overlay: Option<bool>,
    pub layout_strategy: Option<LayoutStrategy>,
    pub draw_order: Option<Vec<String>>,

    pub no_arrows: Option<bool>,
    pub arrow_size: Option<f32>,
    pub arrow_style: Option<ArrowStyle>,

    pub loop_radius: Option<f32>,
    pub loop_angle: Option<f32>,

    pub fanout_max_bend: Option<f32>,
    pub fanout_mode: Option<FanoutMode>,
    pub no_fanout: Option<bool>,

    pub stroke_width: Option<f32>,
}

impl ProfileConfig {
    /// Apply any set fields onto an existing profile.
    pub fn apply_to(&self, profile: &mut RenderProfile) {
        if let Some(t) = &self.title {
            profile.title = Some(t.clone());
        }
        if let Some(d) = self.show_debug_overlay {
            profile.show_debug_overlay = d;
        }
        if let Some(ls) = &self.layout_strategy {
            profile.layout = ls.clone();
        }
        if let Some(order) = &self.draw_order {
            profile.draw_order = order.clone();
        }

        if let Some(n) = self.no_arrows {
            profile.no_arrows = n;
        }
        if let Some(sz) = self.arrow_size {
            profile.arrow_size = sz;
        }
        if let Some(st) = &self.arrow_style {
            profile.arrow_style = st.clone();
        }

        if let Some(r) = self.loop_radius {
            profile.loop_radius = r;
        }
        if let Some(a) = self.loop_angle {
            profile.loop_angle = a;
        }

        if let Some(m) = self.fanout_max_bend {
            profile.fanout_max_bend = m;
        }
        if let Some(mode) = &self.fanout_mode {
            profile.fanout_mode = mode.clone();
        }
        if let Some(off) = self.no_fanout {
            profile.no_fanout = off;
        }
	if let Some(sw) = self.stroke_width {
	    profile.stroke_width = sw;
	}
    }
}