use std::{fmt::Display, rc::Rc};
use figfont::{FIGfont, Layout, SubCharacter};
use crate::{figchar::FIGchar, smush_subchars::smush_subchars};
#[derive(Debug, Clone)]
pub struct FIGline {
font: Rc<FIGfont>,
chars: Vec<FIGchar>,
}
impl FIGline {
pub fn new(text: &str, font: Rc<FIGfont>) -> Self {
let mut n = Self::empty(font);
for char in text.chars() {
n.push_char(char);
}
n
}
pub fn empty(font: Rc<FIGfont>) -> Self {
Self {
font,
chars: vec![],
}
}
pub fn is_empty(&self) -> bool {
self.chars.is_empty() || self.chars.iter().all(|c| c.is_whitespace())
}
pub fn push_str(&mut self, s: &str) {
for c in s.chars() {
self.push_char(c);
}
}
fn push_char(&mut self, c: char) {
let figchar = self.font.get(c as i32);
self.chars.push(figchar.into())
}
pub fn width(&self) -> usize {
RenderedFIGline::from(self).width()
}
}
impl Display for FIGline {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let rendered = RenderedFIGline::from(self);
write!(f, "{rendered}")
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct RenderedFIGline {
layout: Layout,
matrix: Vec<Vec<SubCharacter>>,
}
impl RenderedFIGline {
fn empty(font: &FIGfont) -> Self {
Self {
layout: font.header().layout(),
matrix: vec![vec![]; font.header().height()],
}
}
fn push_char(&mut self, c: &FIGchar) {
if self.layout.contains(Layout::HORIZONTAL_KERNING) {
self.kerned_char(c);
} else if self.layout.contains(Layout::HORIZONTAL_SMUSH) {
self.smush_char(c);
} else {
self.append_char(c);
}
}
fn width(&self) -> usize {
self.matrix[0].len()
}
fn append_char(&mut self, c: &FIGchar) {
let new_matrix = c.lines().to_vec();
for (y, mut line) in new_matrix.into_iter().enumerate() {
self.matrix[y].append(&mut line);
}
}
fn kerned_char(&mut self, c: &FIGchar) {
if self.matrix[0].is_empty() {
self.append_char(c);
return;
}
let left_offsets = self.trailing_spaces();
let right_offsets = c.leading_spaces();
let mut offset = c.width();
for y in 0..left_offsets.len() {
let left = left_offsets[y].0;
let right = right_offsets[y].0;
let off = left + right;
offset = offset.min(off);
}
self.add_with_offset(c, offset);
}
fn smush_char(&mut self, c: &FIGchar) {
if self.matrix[0].is_empty() {
self.append_char(c);
return;
}
let left_offsets = self.trailing_spaces();
let right_offsets = c.leading_spaces();
let mut offset = c.width();
for y in 0..left_offsets.len() {
let left = &left_offsets[y];
let right = &right_offsets[y];
let blank = if left.1.is_blank() || right.1.is_blank() {
if self.layout.contains(Layout::HORIZONTAL_HARDBLANK)
&& left.1.is_blank()
&& right.1.is_blank()
{
1
} else {
0
}
} else {
1
};
let off = left.0 + right.0 + blank;
offset = offset.min(off);
}
self.add_with_offset(c, offset);
}
fn add_with_offset(&mut self, c: &FIGchar, offset: usize) {
for (y, line) in c.lines().iter().enumerate() {
let current = &mut self.matrix[y];
for i in 0..offset {
let pos_current = current.len() - offset + i;
if let Some(r) = &line.get(i) {
current[pos_current] = smush_subchars(self.layout, ¤t[pos_current], r);
}
}
let app_from = offset.min(line.len());
current.append(&mut line[app_from..line.len()].to_vec());
}
}
fn trailing_spaces(&self) -> Vec<(usize, SubCharacter)> {
self.matrix
.iter()
.map(|line| {
line.iter()
.rev()
.enumerate()
.find_map(|(i, c)| {
if *c != SubCharacter::Symbol(" ".to_string()) {
Some((i, c.clone()))
} else {
None
}
})
.unwrap_or((line.len(), SubCharacter::Blank))
})
.collect()
}
}
impl From<&FIGline> for RenderedFIGline {
fn from(value: &FIGline) -> Self {
let mut n = RenderedFIGline::empty(&value.font);
for c in &value.chars {
n.push_char(c);
}
n
}
}
impl Display for RenderedFIGline {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (i, line) in self.matrix.iter().enumerate() {
let line_str: String = line.iter().map(|c| c.to_string()).collect();
write!(f, "{}", &line_str)?;
if i < self.matrix.len() - 1 {
writeln!(f)?;
}
}
Ok(())
}
}