use crate::{
buffer::{Cell, CellGrid, Span},
fragment::Bounds,
FragmentSpan, Point,
};
use sauron::{html::*, svg, svg::attributes::*, Node};
use std::{borrow::Cow, cmp::Ordering, fmt};
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct CellText {
pub start: Cell,
pub content: String,
}
impl CellText {
pub fn new(start: Cell, content: String) -> Self {
CellText { start, content }
}
fn end_cell(&self) -> Cell {
Cell::new(self.start.x + self.content.len() as i32, self.start.y)
}
fn cells(&'_ self) -> impl IntoIterator<Item = Cell> + '_ {
let range = self.start.x..(self.start.x + self.content.len() as i32);
range.map(move |x| Cell::new(x, self.start.y))
}
fn is_adjacent_cell(&self, other_cell: Cell) -> bool {
self.cells()
.into_iter()
.any(|cell| cell.y == other_cell.y && cell.is_adjacent(&other_cell))
}
pub(crate) fn is_contacting(&self, other: &Self) -> bool {
self.cells()
.into_iter()
.any(|cell| other.is_adjacent_cell(cell))
}
pub(crate) fn can_merge(&self, other: &Self) -> bool {
self.start.y == other.start.y
&& (self.start.x + self.content.len() as i32 == other.start.x
|| other.start.x + other.content.len() as i32 == self.start.x)
}
pub(crate) fn merge(&self, other: &Self) -> Option<Self> {
if self.can_merge(other) {
if self.start.x < other.start.x {
Some(CellText::new(
self.start,
format!("{}{}", self.content, other.content),
))
} else {
Some(CellText::new(
other.start,
format!("{}{}", other.content, self.content),
))
}
} else {
None
}
}
pub(crate) fn absolute_position(&self, cell: Cell) -> Self {
CellText {
start: Cell::new(self.start.x + cell.x, self.start.y + cell.y),
..self.clone()
}
}
}
impl Bounds for CellText {
fn bounds(&self) -> (Point, Point) {
(
self.start.top_left_most(),
self.end_cell().bottom_right_most(),
)
}
}
impl From<CellText> for Text {
fn from(ct: CellText) -> Text {
Text::new(ct.start.q(), ct.content)
}
}
impl<MSG> From<CellText> for Node<MSG> {
fn from(ct: CellText) -> Node<MSG> {
let text: Text = ct.into();
text.into()
}
}
impl From<CellText> for FragmentSpan {
fn from(ct: CellText) -> FragmentSpan {
let cells: Vec<(Cell, char)> = ct
.content
.chars()
.into_iter()
.enumerate()
.map(|(i, ch)| (Cell::new(ct.start.x + i as i32, ct.start.y), ch))
.collect();
FragmentSpan::new(Span::from(cells), ct.into())
}
}
impl fmt::Display for CellText {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "CT {} {}", self.start, self.content)
}
}
#[derive(Debug, Clone)]
pub struct Text {
pub start: Point,
pub text: String,
}
impl Text {
pub fn new(start: Point, text: String) -> Self {
Text { start, text }
}
fn text_width(&self) -> f32 {
self.text.len() as f32 * CellGrid::width()
}
pub(crate) fn absolute_position(&self, cell: Cell) -> Self {
Text {
start: cell.absolute_position(self.start),
..self.clone()
}
}
pub(crate) fn scale(&self, scale: f32) -> Self {
Text {
start: self.start.scale(scale),
..self.clone()
}
}
}
fn replace_html_char<'a>(ch: char) -> Cow<'a, str> {
match ch {
'>' => Cow::from(">"),
'<' => Cow::from("<"),
'&' => Cow::from("&"),
'\'' => Cow::from("'"),
'"' => Cow::from("""),
'\0' => Cow::from(""),
_ => Cow::from(ch.to_string()),
}
}
fn escape_html_text(s: &str) -> String {
s.chars().map(replace_html_char).collect()
}
impl fmt::Display for Text {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "T {} {}", self.start, self.text)
}
}
impl<MSG> From<Text> for Node<MSG> {
fn from(t: Text) -> Node<MSG> {
svg::tags::text(
[x(t.start.x), y(t.start.y)],
#[cfg(not(feature = "with-dom"))]
[text(escape_html_text(&t.text))],
#[cfg(feature = "with-dom")]
[text(&t.text)],
)
}
}
impl Bounds for Text {
fn bounds(&self) -> (Point, Point) {
(
self.start,
Point::new(self.start.x + self.text_width(), self.start.y),
)
}
}
impl Eq for Text {}
impl Ord for Text {
fn cmp(&self, other: &Self) -> Ordering {
self.start
.cmp(&other.start)
.then(self.text.cmp(&other.text))
}
}
impl PartialOrd for Text {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Text {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
buffer::{Cell, CellBuffer, Contacts, Span},
fragment::CellText,
Settings,
};
#[test]
fn test_cell_text_absolute_position() {
let t1 = CellText::new(Cell::new(0, 0), "c".to_string());
let t1_abs = t1.absolute_position(Cell::new(1, 1));
assert_eq!(t1_abs.start, Cell::new(1, 1));
let t2 = CellText::new(Cell::new(4, 3), "c".to_string());
let t2_abs = t2.absolute_position(Cell::new(5, 7));
assert_eq!(t2_abs.start, Cell::new(9, 10));
}
#[test]
fn text_merge_point_base() {
let c1 = Cell::new(1, 1);
let c2 = Cell::new(3, 1);
let t1 = CellText::new(c1, "He".to_string());
let t2 = CellText::new(c2, "llo".to_string());
assert!(t1.can_merge(&t2));
assert_eq!(t2.merge(&t1), t1.merge(&t2));
assert_eq!(t1.merge(&t2), Some(CellText::new(c1, "Hello".to_string())));
}
#[test]
fn test_paragraph() {
let art = r#"
This is some sort of a paragraph
with long words such as supercalifragilisticexpialiducios
and pneumonoultramicrospocisilicovulcanoconiosis
"#;
let cell_buffer = CellBuffer::from(art);
let mut spans: Vec<Span> = cell_buffer.into();
assert_eq!(spans.len(), 1);
let span1 = spans.remove(0);
let groups: Vec<Contacts> = span1.localize().into();
for (i, group) in groups.iter().enumerate() {
println!("group{}\n{}", i, group);
}
assert_eq!(15, groups.len());
assert_eq!(
groups[0],
Contacts::new(Into::<FragmentSpan>::into(CellText::new(
Cell::new(0, 0),
"This".to_string()
)))
);
assert_eq!(
groups[5],
Contacts::new(
CellText::new(Cell::new(21, 0), "a".to_string()).into()
)
);
assert_eq!(
groups[14],
Contacts::new(
CellText::new(
Cell::new(4, 2),
"pneumonoultramicrospocisilicovulcanoconiosis".to_string()
)
.into()
)
);
}
#[test]
fn test_2paragraph() {
let art = r#"
This is some sort of a paragraph
with long words such as supercalifragilisticexpialiducios
and pneumonoultramicrospocisilicovulcanoconiosis
Loren ipsum is a second paragrapah.
This is in one span since all the letters
are adjacent since they are right next to
each other taking account the top and bottom
neighbor cell
"#;
let cell_buffer = CellBuffer::from(art);
let mut spans: Vec<Span> = cell_buffer.into();
assert_eq!(spans.len(), 2);
let groups2: Vec<Contacts> = spans.remove(1).localize().into();
let groups1: Vec<Contacts> = spans.remove(0).localize().into();
println!("span1 groups:");
for (i, group1) in groups1.iter().enumerate() {
println!("\tgroup {} {}", i, group1);
}
println!("span2 groups:");
for (i, group2) in groups2.iter().enumerate() {
println!("\tgroup {} {}", i, group2);
}
assert_eq!(groups1.len(), 15);
assert_eq!(groups2.len(), 33);
assert_eq!(
groups1[0].as_ref()[0]
.fragment
.as_cell_text()
.unwrap()
.start,
Cell::new(0, 0)
);
assert_eq!(
groups2[0].as_ref()[0]
.fragment
.as_cell_text()
.unwrap()
.start,
Cell::new(0, 0)
);
}
}