rocketsplash-rt 0.2.2

Runtime library for loading and rendering Rocketsplash assets (.rst, .rsf)
Documentation
// <FILE>crates/rocketsplash-rt/src/font/fnc_render_text.rs</FILE>
// <DESC>Render text into a render buffer</DESC>
// <VERS>VERSION: 1.1.0</VERS>
// <WCTX>Public release refactor audit</WCTX>
// <CLOG>Extract alignment, glyph resolution, and stamping helpers</CLOG>

use std::cmp::{max, min};

use super::fnc_align_offset::align_offset;
use super::fnc_resolve_glyph::resolve_glyph;
use super::fnc_stamp_glyph::stamp_glyph;
use crate::font::{Font, RenderOptions, RuntimeGlyph};
use crate::{Error, RenderBuffer, TextStyle};

pub(crate) fn render_text(
    font: &Font,
    text: &str,
    options: &RenderOptions,
) -> Result<RenderBuffer, Error> {
    if text.is_empty() {
        return Ok(RenderBuffer::new(0, 0));
    }

    let spacing = options.spacing as i64;
    let output_style = options.style & !TextStyle::REVERSE;
    let mut layouts = Vec::new();
    let mut max_width = 0i64;
    for line in text.split('\n') {
        let layout = layout_line(font, line, options, spacing)?;
        max_width = max(max_width, layout.width);
        layouts.push(layout);
    }

    let line_height = font.line_height.max(1) as i64;
    let total_height = line_height * layouts.len() as i64;
    if max_width == 0 || total_height == 0 {
        return Ok(RenderBuffer::new(0, 0));
    }
    let mut buffer = RenderBuffer::new(max_width as usize, total_height as usize);

    for (line_idx, layout) in layouts.iter().enumerate() {
        let align_offset = align_offset(options.align, max_width, layout.width);
        let y_offset = line_idx as i64 * line_height;
        for placement in &layout.placements {
            let x_offset = placement.x + align_offset;
            stamp_glyph(
                &mut buffer,
                placement.glyph,
                x_offset,
                y_offset,
                output_style,
            );
        }
    }

    Ok(buffer)
}

struct LineLayout<'a> {
    placements: Vec<Placement<'a>>,
    width: i64,
}

struct Placement<'a> {
    x: i64,
    glyph: &'a RuntimeGlyph,
}

fn layout_line<'a>(
    font: &'a Font,
    line: &str,
    options: &RenderOptions,
    spacing: i64,
) -> Result<LineLayout<'a>, Error> {
    let mut placements = Vec::new();
    let mut cursor = 0i64;
    let mut min_x = 0i64;
    let mut max_x = 0i64;

    for ch in line.chars() {
        let glyph = resolve_glyph(font, ch, options)?;
        let Some(glyph) = glyph else {
            continue;
        };
        placements.push(Placement { x: cursor, glyph });
        min_x = min(min_x, cursor);
        max_x = max(max_x, cursor + glyph.width as i64);
        cursor += glyph.width as i64 + spacing;
    }

    if placements.is_empty() {
        return Ok(LineLayout {
            placements,
            width: 0,
        });
    }

    let shift = if min_x < 0 { -min_x } else { 0 };
    if shift > 0 {
        for placement in &mut placements {
            placement.x += shift;
        }
        max_x += shift;
    }

    Ok(LineLayout {
        placements,
        width: max_x,
    })
}

// <FILE>crates/rocketsplash-rt/src/font/fnc_render_text.rs</FILE>
// <VERS>END OF VERSION: 1.1.0</VERS>