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,
})
}