rocketsplash-rt 0.2.2

Runtime library for loading and rendering Rocketsplash assets (.rst, .rsf)
Documentation
// <FILE>crates/rocketsplash-rt/src/render/cls_ansi_state.rs</FILE>
// <DESC>Stateful ANSI style/color transition writer</DESC>
// <VERS>VERSION: 1.0.0</VERS>
// <WCTX>Public release refactor audit</WCTX>
// <CLOG>Extract ANSI transition state from fnc_to_ansi</CLOG>

use std::io::{self, Write};

use rocketsplash_formats::Rgb;

use crate::{rgb_to_16_bg, rgb_to_16_fg, rgb_to_256, ColorMode, RenderCell, TextStyle};

const STYLE_CODES: [(TextStyle, u8, u8); 4] = [
    (TextStyle::BOLD, 1, 22),
    (TextStyle::ITALIC, 3, 23),
    (TextStyle::UNDERLINE, 4, 24),
    (TextStyle::REVERSE, 7, 27),
];

#[derive(Clone, Copy, Debug, Default)]
pub(super) struct AnsiState {
    fg: Option<Rgb>,
    bg: Option<Rgb>,
    style: TextStyle,
}

impl AnsiState {
    pub(super) fn is_default(&self) -> bool {
        self.fg.is_none() && self.bg.is_none() && self.style.is_empty()
    }

    pub(super) fn apply<W: Write>(
        &mut self,
        writer: &mut W,
        cell: &RenderCell,
        mode: ColorMode,
    ) -> io::Result<()> {
        let mut first = true;
        macro_rules! write_sep {
            () => {
                if first {
                    writer.write_all(b"\x1b[")?;
                    first = false;
                } else {
                    writer.write_all(b";")?;
                }
            };
        }

        let next_style = cell.style;
        if self.style != next_style {
            for (flag, on_code, off_code) in STYLE_CODES {
                let had = self.style.contains(flag);
                let has = next_style.contains(flag);
                if had && !has {
                    write_sep!();
                    write!(writer, "{}", off_code)?;
                } else if !had && has {
                    write_sep!();
                    write!(writer, "{}", on_code)?;
                }
            }
            self.style = next_style;
        }
        if self.fg != cell.fg {
            write_sep!();
            push_color_code(writer, cell.fg, mode, true)?;
            self.fg = cell.fg;
        }
        if self.bg != cell.bg {
            write_sep!();
            push_color_code(writer, cell.bg, mode, false)?;
            self.bg = cell.bg;
        }
        if !first {
            writer.write_all(b"m")?;
        }
        Ok(())
    }
}

fn push_color_code<W: Write>(
    writer: &mut W,
    color: Option<Rgb>,
    mode: ColorMode,
    is_fg: bool,
) -> io::Result<()> {
    match (color, mode) {
        (None, _) => write!(writer, "{}", if is_fg { "39" } else { "49" }),
        (Some(rgb), ColorMode::TrueColor) => {
            let prefix = if is_fg { "38" } else { "48" };
            write!(writer, "{};2;{};{};{}", prefix, rgb.r, rgb.g, rgb.b)
        }
        (Some(rgb), ColorMode::Color256) => {
            let prefix = if is_fg { "38" } else { "48" };
            write!(writer, "{};5;{}", prefix, rgb_to_256(rgb))
        }
        (Some(rgb), ColorMode::Color16) => {
            let code = if is_fg {
                rgb_to_16_fg(rgb)
            } else {
                rgb_to_16_bg(rgb)
            };
            write!(writer, "{}", code)
        }
        (Some(_), ColorMode::NoColor) => Ok(()),
    }
}

// <FILE>crates/rocketsplash-rt/src/render/cls_ansi_state.rs</FILE>
// <VERS>END OF VERSION: 1.0.0</VERS>