use crossterm::style::Color;
use unicode_segmentation::UnicodeSegmentation;
use crate::{
graphics::{FontStyle, grapheme_width},
html::css_parse::TextAlign,
layout::{InlineRun, Rect},
};
use super::{BackgroundFill, PaintCanvas, background_at, text_color_at};
#[allow(clippy::too_many_arguments)]
pub(crate) fn paint_runs(
canvas: &mut PaintCanvas<'_>,
lines: &[Vec<InlineRun>],
cell_start: usize,
block_width: usize,
align: TextAlign,
effective_bg: Option<BackgroundFill<'_>>,
text_fill: Option<BackgroundFill<'_>>,
font_size_cells: usize,
line_height_cells: usize,
clip: Rect,
h_bound_cols: usize,
first_line_indent: usize,
) {
if lines.is_empty() {
return;
}
let cols = canvas.columns as usize;
let line_h = line_height_cells.max(1);
let first_line_row = cell_start / cols;
for (line_idx, line_runs) in lines.iter().enumerate() {
let line_row = first_line_row.saturating_add(line_idx.saturating_mul(line_h));
if line_row < clip.y || line_row >= clip.y.saturating_add(clip.height) {
continue;
}
if line_runs.is_empty() {
continue;
}
let line_start =
cell_start.saturating_add(line_idx.saturating_mul(line_h).saturating_mul(cols));
let line_row_base = line_start.saturating_sub(line_start % cols);
let clip_right = clip.x.saturating_add(clip.width).min(cols);
let h_end = line_row_base.saturating_add(h_bound_cols.min(clip_right));
let indent = if line_idx == 0 { first_line_indent } else { 0 };
let indent = indent.min(block_width);
paint_one_line(
canvas,
line_runs,
line_start.saturating_add(indent),
block_width.saturating_sub(indent),
align,
effective_bg,
text_fill,
font_size_cells,
h_end,
);
}
}
#[allow(clippy::too_many_arguments)]
fn paint_one_line(
canvas: &mut PaintCanvas<'_>,
runs: &[InlineRun],
cell_start: usize,
block_width: usize,
align: TextAlign,
effective_bg: Option<BackgroundFill<'_>>,
text_fill: Option<BackgroundFill<'_>>,
font_size_cells: usize,
h_end: usize,
) {
let total_width: usize = runs.iter().map(|r| r.width_at(font_size_cells)).sum();
let total_width = total_width.min(block_width);
let offset = match align {
TextAlign::Left => 0,
TextAlign::Center => block_width.saturating_sub(total_width) / 2,
TextAlign::Right => block_width.saturating_sub(total_width),
};
let row_end = h_end;
let mut index = cell_start + offset;
for run in runs {
if run.is_empty() {
continue;
}
paint_text_row(
canvas,
index,
&run.text,
run.fg,
run.bg,
effective_bg,
text_fill,
run.style,
font_size_cells,
row_end,
run.letter_spacing,
run.word_spacing,
);
index = index.saturating_add(run.width_at(font_size_cells));
}
}
#[allow(clippy::too_many_arguments)]
fn paint_text_row(
canvas: &mut PaintCanvas<'_>,
start: usize,
text: &str,
fg: Option<Color>,
run_bg: Option<Color>,
inherited_bg: Option<BackgroundFill<'_>>,
text_fill: Option<BackgroundFill<'_>>,
style: FontStyle,
font_size_cells: usize,
row_end: usize,
letter_spacing: usize,
word_spacing: isize,
) {
let max = canvas.layout.len();
let cols = canvas.columns as usize;
let mut idx = start;
let font_size = font_size_cells.max(1);
for g in text.graphemes(true) {
let w = grapheme_width(g);
let word_extra = if g == " " {
word_spacing.max(0) as usize
} else {
0
};
let slot_width = w.max(font_size) + word_extra + letter_spacing;
if idx.saturating_add(slot_width) > row_end {
break;
}
if idx >= max {
break;
}
if w == 2 && idx + 1 >= max {
break;
}
if !canvas.in_region(idx) {
idx += slot_width;
continue;
}
let cell = &mut canvas.layout[idx];
cell.word.clear();
cell.word.push_str(g);
cell.fcolor = text_color_at(fg, text_fill, idx % cols, idx / cols);
cell.bcolor = run_bg.or_else(|| background_at(inherited_bg, idx % cols, idx / cols));
cell.style = style;
if w == 2 {
if canvas.in_region(idx + 1) {
let pad = &mut canvas.layout[idx + 1];
pad.word.clear();
pad.fcolor = text_color_at(fg, text_fill, (idx + 1) % cols, (idx + 1) / cols);
pad.bcolor = run_bg
.or_else(|| background_at(inherited_bg, (idx + 1) % cols, (idx + 1) / cols));
pad.style = style;
}
}
for pad in 1..font_size {
let target = idx + pad;
if target >= row_end {
break;
}
if target >= max {
break;
}
if w == 2 && pad == 1 {
continue;
}
if !canvas.in_region(target) {
continue;
}
let pad_cell = &mut canvas.layout[target];
pad_cell.word.clear();
pad_cell.word.push(' ');
pad_cell.fcolor = text_color_at(fg, text_fill, target % cols, target / cols);
pad_cell.bcolor =
run_bg.or_else(|| background_at(inherited_bg, target % cols, target / cols));
pad_cell.style = style;
}
idx += slot_width;
}
}