use std::cell::RefCell;
use crate::core::{Fragment, Word};
use crate::wrap_algorithms::WrapAlgorithm;
#[derive(Clone, Copy, Debug)]
pub struct OptimalFit {
pub nline_penalty: i32,
pub overflow_penalty: i32,
pub short_last_line_fraction: usize,
pub short_last_line_penalty: i32,
pub hyphen_penalty: i32,
}
impl OptimalFit {
pub const fn new() -> Self {
OptimalFit {
nline_penalty: 1000,
overflow_penalty: 50 * 50,
short_last_line_fraction: 4,
short_last_line_penalty: 25,
hyphen_penalty: 25,
}
}
}
impl Default for OptimalFit {
fn default() -> Self {
Self::new()
}
}
impl WrapAlgorithm for OptimalFit {
#[inline]
fn wrap<'a, 'b>(&self, words: &'b [Word<'a>], line_widths: &'b [usize]) -> Vec<&'b [Word<'a>]> {
wrap_optimal_fit(words, line_widths, &self)
}
}
struct LineNumbers {
line_numbers: RefCell<Vec<usize>>,
}
impl LineNumbers {
fn new(size: usize) -> Self {
let mut line_numbers = Vec::with_capacity(size);
line_numbers.push(0);
LineNumbers {
line_numbers: RefCell::new(line_numbers),
}
}
fn get<T>(&self, i: usize, minima: &[(usize, T)]) -> usize {
while self.line_numbers.borrow_mut().len() < i + 1 {
let pos = self.line_numbers.borrow().len();
let line_number = 1 + self.get(minima[pos].0, &minima);
self.line_numbers.borrow_mut().push(line_number);
}
self.line_numbers.borrow()[i]
}
}
pub fn wrap_optimal_fit<'a, 'b, T: Fragment>(
fragments: &'a [T],
line_widths: &'b [usize],
penalties: &'b OptimalFit,
) -> Vec<&'a [T]> {
let default_line_width = line_widths.last().copied().unwrap_or(0);
let mut widths = Vec::with_capacity(fragments.len() + 1);
let mut width = 0;
widths.push(width);
for fragment in fragments {
width += fragment.width() + fragment.whitespace_width();
widths.push(width);
}
let line_numbers = LineNumbers::new(fragments.len());
let minima = smawk::online_column_minima(0, widths.len(), |minima, i, j| {
let line_number = line_numbers.get(i, &minima);
let line_width = line_widths
.get(line_number)
.copied()
.unwrap_or(default_line_width);
let target_width = std::cmp::max(1, line_width);
let line_width = widths[j] - widths[i] - fragments[j - 1].whitespace_width()
+ fragments[j - 1].penalty_width();
let mut cost = minima[i].1 + penalties.nline_penalty;
if line_width > target_width {
let overflow = (line_width - target_width) as i32;
cost += overflow * penalties.overflow_penalty;
} else if j < fragments.len() {
let gap = (target_width - line_width) as i32;
cost += gap * gap;
} else if i + 1 == j && line_width < target_width / penalties.short_last_line_fraction {
cost += penalties.short_last_line_penalty;
}
if fragments[j - 1].penalty_width() > 0 {
cost += penalties.hyphen_penalty;
}
cost
});
let mut lines = Vec::with_capacity(line_numbers.get(fragments.len(), &minima));
let mut pos = fragments.len();
loop {
let prev = minima[pos].0;
lines.push(&fragments[prev..pos]);
pos = prev;
if pos == 0 {
break;
}
}
lines.reverse();
lines
}