rari 0.13.6

rari: Runtime Accelerated Rendering Infrastructure - A high-performance React Server Components framework powered by Rust
Documentation
use super::super::layout::ComputedLayout;
use super::super::resources::fonts::FontContext;
use super::super::types::JsxChild;
use super::mask::MaskMemory;
use image::{Rgba, RgbaImage};
use parley::FontContext as ParleyFontContext;
use rustc_hash::FxHashMap;
use zeno::Scratch;

pub struct ImageRenderer {
    pub(super) width: u32,
    pub(super) height: u32,
    pub(super) font_context: ParleyFontContext,
    pub(super) scratch: Scratch,
    pub(super) mask_memory: MaskMemory,
}

impl ImageRenderer {
    pub fn new(width: u32, height: u32, font_context: FontContext) -> Self {
        Self {
            width,
            height,
            font_context: font_context.inner,
            scratch: Scratch::new(),
            mask_memory: MaskMemory::default(),
        }
    }

    pub fn render(&mut self, layout: &ComputedLayout) -> Result<RgbaImage, String> {
        let mut image = RgbaImage::new(self.width, self.height);

        for pixel in image.pixels_mut() {
            *pixel = Rgba([255, 255, 255, 255]);
        }

        self.render_node(layout, &mut image)?;

        Ok(image)
    }

    fn render_node(
        &mut self,
        layout: &ComputedLayout,
        image: &mut RgbaImage,
    ) -> Result<(), String> {
        if let Some(bg) = layout.style.get("background").or(layout.style.get("backgroundColor")) {
            self.render_background(layout, bg, image, &mut self.mask_memory.clone())?;
        }

        match layout.element.element_type.as_str() {
            "img" => {
                self.render_image(layout, image)?;
            }
            "svg" => {
                self.render_svg(layout, image)?;
            }
            _ => {
                if self.has_text_content(&layout.element) {
                    self.render_text(layout, image)?;
                }
            }
        }

        self.render_border(layout, image, &mut self.mask_memory.clone())?;

        for child in &layout.children {
            self.render_node(child, image)?;
        }

        Ok(())
    }

    pub(super) fn alpha_blend(&self, bg: Rgba<u8>, fg: Rgba<u8>) -> Rgba<u8> {
        let alpha = fg[3] as f32 / 255.0;
        let inv_alpha = 1.0 - alpha;

        Rgba([
            ((fg[0] as f32 * alpha + bg[0] as f32 * inv_alpha) as u8),
            ((fg[1] as f32 * alpha + bg[1] as f32 * inv_alpha) as u8),
            ((fg[2] as f32 * alpha + bg[2] as f32 * inv_alpha) as u8),
            255,
        ])
    }

    fn has_text_content(&self, element: &super::super::types::JsxElement) -> bool {
        element.children.iter().any(|child| matches!(child, JsxChild::Text(_)))
    }

    pub(super) fn parse_font_weight(&self, style: &FxHashMap<String, String>) -> u16 {
        style
            .get("fontWeight")
            .and_then(|w| match w.as_str() {
                "normal" => Some(400),
                "bold" => Some(700),
                "100" => Some(100),
                "200" => Some(200),
                "300" => Some(300),
                "400" => Some(400),
                "500" => Some(500),
                "600" => Some(600),
                "700" => Some(700),
                "800" => Some(800),
                "900" => Some(900),
                _ => w.parse::<u16>().ok(),
            })
            .unwrap_or(400)
    }

    pub(super) fn parse_color(&self, color_str: &str) -> Rgba<u8> {
        match color_str {
            "black" => Rgba([0, 0, 0, 255]),
            "white" => Rgba([255, 255, 255, 255]),
            "red" => Rgba([255, 0, 0, 255]),
            "green" => Rgba([0, 255, 0, 255]),
            "blue" => Rgba([0, 0, 255, 255]),
            _ if color_str.starts_with('#') => {
                let hex = color_str.trim_start_matches('#');
                if hex.len() == 6 {
                    let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0);
                    let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0);
                    let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0);
                    Rgba([r, g, b, 255])
                } else {
                    Rgba([0, 0, 0, 255])
                }
            }
            _ if color_str.starts_with("rgb(") => {
                let inner = color_str.trim_start_matches("rgb(").trim_end_matches(')');
                let parts: Vec<&str> = inner.split(',').map(|s| s.trim()).collect();
                if parts.len() == 3 {
                    let r = parts[0].parse().unwrap_or(0);
                    let g = parts[1].parse().unwrap_or(0);
                    let b = parts[2].parse().unwrap_or(0);
                    Rgba([r, g, b, 255])
                } else {
                    Rgba([0, 0, 0, 255])
                }
            }
            _ => Rgba([0, 0, 0, 255]),
        }
    }
}