bland 0.1.0

Pure-Rust library for paper-ready, monochrome, hatch-patterned technical plots in the visual tradition of 1960s-80s engineering reports.
Documentation
//! Theme presets and defaults.

/// Title case transform applied to figure titles by some themes.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TitleCase {
    None,
    Upper,
    Lower,
}

/// Tick mark direction relative to the plotting frame.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TickDirection {
    In,
    Out,
    Both,
}

/// A theme is a bag of typographic, geometric, and stroke defaults.
/// Every figure carries one. Use [`Theme::report_1972`],
/// [`Theme::blueprint`], or [`Theme::gazette`] for the built-in
/// presets, then mutate fields directly to derive variants.
#[derive(Debug, Clone)]
pub struct Theme {
    pub name: &'static str,
    // Typography
    pub font_family: &'static str,
    pub title_font_family: &'static str,
    pub label_font_family: &'static str,
    pub title_font_size: f64,
    pub subtitle_font_size: f64,
    pub axis_label_font_size: f64,
    pub tick_label_font_size: f64,
    pub legend_font_size: f64,
    pub annotation_font_size: f64,
    pub title_letter_spacing: &'static str,
    pub title_case: TitleCase,
    // Colors
    pub foreground: &'static str,
    pub background: &'static str,
    // Strokes
    pub axis_stroke_width: f64,
    pub frame_stroke_width: f64,
    pub grid_stroke_width: f64,
    pub grid_dasharray: &'static str,
    pub series_stroke_width: f64,
    pub tick_length: f64,
    pub tick_minor_length: f64,
    pub tick_direction: TickDirection,
    pub tick_stroke_width: f64,
    // Frame around plotting area
    pub frame: bool,
    // Page border around full canvas
    pub border: bool,
    pub border_stroke_width: f64,
    pub border_inset: f64,
    // Legend
    pub legend_frame: bool,
    pub legend_stroke_width: f64,
    // Marker defaults
    pub marker_size: f64,
    pub marker_stroke_width: f64,
}

impl Theme {
    /// Default: serif body, thin black rules, inward ticks, framed plot.
    pub fn report_1972() -> Self {
        Self {
            name: "report_1972",
            font_family: "Times, 'Liberation Serif', serif",
            title_font_family: "Times, 'Liberation Serif', serif",
            label_font_family: "Times, 'Liberation Serif', serif",
            title_font_size: 14.0,
            subtitle_font_size: 11.0,
            axis_label_font_size: 11.0,
            tick_label_font_size: 9.0,
            legend_font_size: 10.0,
            annotation_font_size: 9.0,
            title_letter_spacing: "0.05em",
            title_case: TitleCase::Upper,
            foreground: "black",
            background: "white",
            axis_stroke_width: 1.0,
            frame_stroke_width: 1.0,
            grid_stroke_width: 0.4,
            grid_dasharray: "2 3",
            series_stroke_width: 1.2,
            tick_length: 4.0,
            tick_minor_length: 2.0,
            tick_direction: TickDirection::In,
            tick_stroke_width: 1.0,
            frame: true,
            border: true,
            border_stroke_width: 0.8,
            border_inset: 12.0,
            legend_frame: true,
            legend_stroke_width: 0.8,
            marker_size: 4.0,
            marker_stroke_width: 1.0,
        }
    }

    /// Courier-style monospace labels, borderless axes, thicker strokes.
    pub fn blueprint() -> Self {
        Self {
            name: "blueprint",
            font_family: "'Courier New', 'Liberation Mono', monospace",
            title_font_family: "'Courier New', 'Liberation Mono', monospace",
            label_font_family: "'Courier New', 'Liberation Mono', monospace",
            title_font_size: 13.0,
            axis_label_font_size: 10.0,
            tick_label_font_size: 9.0,
            axis_stroke_width: 1.4,
            frame_stroke_width: 1.4,
            series_stroke_width: 1.4,
            grid_dasharray: "1 4",
            grid_stroke_width: 0.5,
            frame: false,
            border: true,
            border_inset: 8.0,
            tick_direction: TickDirection::In,
            tick_length: 6.0,
            ..Self::report_1972()
        }
    }

    /// Compact, high-contrast — closer to a newspaper science column.
    pub fn gazette() -> Self {
        Self {
            name: "gazette",
            font_family: "Georgia, 'Liberation Serif', serif",
            title_font_family: "Georgia, 'Liberation Serif', serif",
            title_font_size: 16.0,
            subtitle_font_size: 12.0,
            tick_label_font_size: 10.0,
            axis_label_font_size: 12.0,
            title_letter_spacing: "0.02em",
            title_case: TitleCase::None,
            frame: true,
            border: false,
            grid_dasharray: "1 5",
            series_stroke_width: 1.3,
            tick_length: 4.0,
            tick_direction: TickDirection::Out,
            ..Self::report_1972()
        }
    }

    pub fn transform_title(&self, text: &str) -> String {
        match self.title_case {
            TitleCase::None => text.to_string(),
            TitleCase::Upper => text.to_uppercase(),
            TitleCase::Lower => text.to_lowercase(),
        }
    }
}

impl Default for Theme {
    fn default() -> Self {
        Theme::report_1972()
    }
}