use crate::errors::*;
use crate::plane::{Cell, Plane};
use crate::point::{Point, Points, POINT_ZERO};
use crate::rect::{Rect, Rectangles};
use dmntk_model::dmntk_feel_parser::dmntk_feel::dmntk_common::Result;
pub const LAYER_COUNT: usize = 4;
pub const LAYER_NAMES: [&str; LAYER_COUNT] = ["TEXT", "THIN", "BODY", "GRID"];
pub const LAYER_TEXT: usize = 0;
pub const LAYER_THIN: usize = 1;
pub const LAYER_BODY: usize = 2;
pub const LAYER_GRID: usize = 3;
const CHAR_WHITE: char = ' ';
const CHAR_OUTER: char = '░';
const CORNER_TOP_LEFT: char = '┌';
const CORNER_BOTTOM_RIGHT: char = '┘';
const CORNERS_TOP_LEFT: [char; 4] = ['┌', '├', '┬', '┼'];
const CORNERS_TOP_RIGHT: [char; 4] = ['┐', '┤', '┬', '┼'];
const CORNERS_BOTTOM_RIGHT: [char; 4] = ['┘', '┤', '┴', '┼'];
const CORNERS_BOTTOM_LEFT: [char; 4] = ['└', '├', '┴', '┼'];
type Layer = usize;
type Layers = [char; LAYER_COUNT];
pub struct Canvas {
content: Vec<Vec<Layers>>,
cursor: Point,
cross: Option<Point>,
cross_horz: Option<Point>,
cross_vert: Option<Point>,
pub information_item_name: Option<String>,
body_rect: Option<Rect>,
}
impl Canvas {
fn recognize_information_item_name(&mut self) -> Result<()> {
let layer = LAYER_TEXT;
self.move_to(POINT_ZERO);
self.search(layer, &['┌']).and_then(|(_, top_left)| {
self.search(layer, &['╥']).and_then(|(_, top_edge)| {
if top_left.y < top_edge.y {
self.move_to(top_left);
self.search_right(layer, &['┐'], &['─']).and_then(|_| {
self.search_down(layer, &['┴', '┤', '┼'], &['│']).and_then(|(_, bottom_right)| {
self.search_left(layer, &['├'], &['─', '┬', '╥']).and_then(|_| {
self.search_up(layer, &['┌'], &['│']).and_then(|(_, closing)| {
self.close_rectangle(closing, top_left, bottom_right).map(|rect| {
let text = self.text_from_rect(layer, &rect);
self.information_item_name = Some(text);
})
})
})
})
})
} else {
Ok(())
}
})
})
}
fn recognize_crossings(&mut self) -> Result<()> {
self.move_to(POINT_ZERO); self.search(LAYER_TEXT, &['╬']).map(|(_, point)| {
self.cross = Some(point);
self.move_to(point);
if let Ok((_, p)) = self.search_right(LAYER_TEXT, &['╬'], &['═', '╪']) {
self.cross_horz = Some(p)
}
self.move_to(point);
if let Ok((_, p)) = self.search_down(LAYER_TEXT, &['╬'], &['║', '╫']) {
self.cross_vert = Some(p)
}
})
}
fn recognize_body_rect(&mut self) -> Result<Rect> {
let layer = LAYER_TEXT;
self.move_to(POINT_ZERO);
self.search(layer, &['╬']).and_then(|(_, cross_point)| {
self.search_up(layer, &['╥'], &['║', '╫', '╟', '╢']).and_then(|(_, top_point)| {
self.search_down(layer, &['╨'], &['║', '╫', '╟', '╢', '╬']).and_then(|(_, bottom_point)| {
self.move_to(cross_point);
self.search_left(layer, &['╞'], &['═', '╪', '╧', '╤']).and_then(|(_, left_point)| {
self
.search_right(layer, &['╡'], &['═', '╪', '╧', '╤', '╬'])
.map(|(_, right_point)| Rect::new(left_point.x, top_point.y, right_point.x + 1, bottom_point.y + 1))
})
})
})
})
}
fn prepare_regions(&mut self, src: Layer, dst: Layer) {
for row in self.content.iter_mut() {
for col in row.iter_mut() {
match col[src] {
'┌' | '┐' | '└' | '┘' | '┬' | '┴' | '─' | '│' | '├' | '┼' | '┤' | ' ' | CHAR_OUTER => col[dst] = col[src],
'╥' | '╤' => col[dst] = '┬',
'║' => col[dst] = '│',
'╨' | '╧' => col[dst] = '┴',
'═' => col[dst] = '─',
'╞' | '╟' => col[dst] = '├',
'╡' | '╢' => col[dst] = '┤',
'╫' | '╪' | '╬' => col[1] = '┼',
_ => col[dst] = CHAR_WHITE,
}
}
}
}
fn remove_information_item_region(&mut self, src: Layer, dst: Layer) {
if let Some(body_rect) = self.body_rect {
let (left, top, right, bottom) = body_rect.unpack();
if top > 0 {
for y in 0..top - 1 {
for x in left..right {
for layer in dst..LAYER_COUNT {
self.content[y][x][layer] = CHAR_OUTER;
}
}
}
}
for x in left..right {
match self.content[top][x][src] {
'├' => self.content[top][x][dst] = '┌',
'┴' => self.content[top][x][dst] = '─',
'┤' => self.content[top][x][dst] = '┐',
'┼' => self.content[top][x][dst] = '┬',
_ => self.content[top][x][dst] = self.content[top][x][src],
}
}
for y in top + 1..bottom {
for x in left..right {
self.content[y][x][dst] = self.content[y][x][src];
}
}
}
}
fn make_grid(&mut self, src: Layer, dst: Layer) {
if let Some(body_rect) = self.body_rect {
self.copy_layer(src, dst);
let (left, top, right, bottom) = body_rect.unpack();
for y in top..bottom {
let mut has_horz_line = false;
for x in left..right {
if self.content[y][x][dst] == '─' {
has_horz_line = true;
break;
}
}
if has_horz_line {
for x in left..right {
match self.content[y][x][dst] {
'│' if x == left => self.content[y][x][dst] = '├',
'│' if x == right - 1 => self.content[y][x][dst] = '┤',
'│' => self.content[y][x][dst] = '┼',
'┤' if x < right - 1 => self.content[y][x][dst] = '┼',
'├' if x > 0 => self.content[y][x][dst] = '┼',
CHAR_WHITE => self.content[y][x][dst] = '─',
_ => {}
}
}
}
}
for x in left..right {
let mut has_vert_line = false;
for y in top..bottom {
if self.content[y][x][dst] == '│' {
has_vert_line = true;
break;
}
}
if has_vert_line {
for y in top..bottom {
match self.content[y][x][dst] {
'─' if y == top => self.content[y][x][dst] = '┬',
'─' if y == bottom - 1 => self.content[y][x][dst] = '┴',
'─' => self.content[y][x][dst] = '┼',
'┴' if y < bottom - 1 => self.content[y][x][dst] = '┼',
'┬' if y > top => self.content[y][x][dst] = '┼',
CHAR_WHITE => self.content[y][x][dst] = '│',
_ => {}
}
}
}
}
}
}
pub fn plane(&mut self) -> Result<Plane> {
let regions = self.recognize_regions()?;
let mut plane: Plane = Default::default(); let mut row = 0; let mut col; let mut width = 0; let mut cross_col = None; let mut cross_horz_col = None; for y in 0..self.content.len() {
if let Some(cross_point) = self.cross {
if y == cross_point.y {
for i in 0..width {
if let Some(index) = cross_col {
if index == i {
plane.add_cell(row, Cell::MainDoubleCrossing);
continue;
}
}
if let Some(index) = cross_horz_col {
if index == i {
plane.add_cell(row, Cell::HorizontalDoubleCrossing);
continue;
}
}
plane.add_cell(row, Cell::HorizontalOutputDoubleLine);
}
plane.add_row();
row += 1;
}
}
if let Some(cross_vert_point) = self.cross_vert {
if y == cross_vert_point.y {
for i in 0..width {
if let Some(index) = cross_col {
if index == i {
plane.add_cell(row, Cell::VerticalDoubleCrossing);
continue;
}
}
plane.add_cell(row, Cell::HorizontalAnnotationsDoubleLine);
}
plane.add_row();
row += 1;
}
}
col = 0;
let mut matched = false;
for x in 0..self.content[y].len() {
if CORNERS_TOP_LEFT.contains(&self.content[y][x][LAYER_GRID]) {
matched = true;
if let Some(point) = self.cross {
if x == point.x {
cross_col = Some(col);
plane.add_cell(row, Cell::VerticalOutputDoubleLine);
col += 1;
}
}
if let Some(point) = self.cross_horz {
if x == point.x {
cross_horz_col = Some(col);
plane.add_cell(row, Cell::VerticalAnnotationDoubleLine);
col += 1;
}
}
let rect = self.recognize_rectangle(LAYER_GRID, Point::new(x, y))?;
let mut found = false;
for (i, region) in regions.iter().enumerate() {
if region.contains(&rect) {
let text = self.text_from_rect(LAYER_TEXT, region);
plane.add_cell(row, Cell::Region(i, *region, text));
found = true;
break;
}
}
if !found {
return Err(canvas_region_not_found(rect));
}
col += 1;
}
}
if matched {
width = plane.row_len(row);
plane.add_row();
row += 1;
}
}
plane.finalize()?;
Ok(plane)
}
fn recognize_regions(&mut self) -> Result<Rectangles> {
let layer = LAYER_THIN;
let points = self.find_top_left_corners(layer);
let mut rectangles = vec![];
for point in points {
let rectangle = self.recognize_region(layer, point)?;
rectangles.push(rectangle);
}
Ok(rectangles)
}
fn recognize_region(&mut self, layer: Layer, top_left: Point) -> Result<Rect> {
self.move_to(top_left);
self.search_right(layer, &CORNERS_TOP_RIGHT, &['─', '┴']).and_then(|_| {
self.search_down(layer, &CORNERS_BOTTOM_RIGHT, &['│', '├']).and_then(|(_, bottom_right)| {
self.search_left(layer, &CORNERS_BOTTOM_LEFT, &['─', '┬']).and_then(|_| {
self
.search_up(layer, &CORNERS_TOP_LEFT, &['│', '┤'])
.and_then(|(_, closing)| self.close_rectangle(closing, top_left, bottom_right))
})
})
})
}
fn recognize_rectangle(&mut self, layer: Layer, top_left: Point) -> Result<Rect> {
self.move_to(top_left);
self.search_right(layer, &['┼', '┬', '┤', '┐'], &['─']).and_then(|_| {
self.search_down(layer, &['┼', '┴', '┤', '┘'], &['│']).and_then(|(_, bottom_right)| {
self.search_left(layer, &['┼', '└', '├', '┴'], &['─']).and_then(|_| {
self
.search_up(layer, &['┼', '┬', '├', '┌'], &['│'])
.and_then(|(_, closing)| self.close_rectangle(closing, top_left, bottom_right))
})
})
})
}
fn close_rectangle(&self, closing: Point, top_left: Point, bottom_right: Point) -> Result<Rect> {
if closing.overlays(top_left) {
Ok(Rect::new(top_left.x, top_left.y, bottom_right.x + 1, bottom_right.y + 1))
} else {
Err(canvas_rectangle_not_closed(closing, top_left))
}
}
fn find_top_left_corners(&self, layer: Layer) -> Points {
let mut points = vec![];
for y in 0..self.content.len() {
for x in 0..self.content[y].len() {
if CORNERS_TOP_LEFT.contains(&self.content[y][x][layer]) {
points.push(Point::new(x, y));
}
}
}
points
}
fn move_to(&mut self, point: Point) {
let row_count = self.content.len();
let y = if point.y < row_count {
point.y
} else if row_count > 0 {
row_count - 1
} else {
0
};
let col_count = self.content[y].len();
let x = if point.x < col_count {
point.x
} else if col_count > 0 {
col_count - 1
} else {
0
};
self.cursor = Point::new(x, y);
}
fn search(&mut self, layer: Layer, searched: &[char]) -> Result<(char, Point)> {
let (x, y) = self.cursor.unwrap();
for c in x..self.content[y].len() {
let ch = self.content[y][c][layer];
if searched.contains(&ch) {
self.cursor = Point::new(c, y);
return Ok((ch, self.cursor));
}
}
for r in y + 1..self.content.len() {
for c in 0..self.content[r].len() {
let ch = self.content[r][c][layer];
if searched.contains(&ch) {
self.cursor = Point::new(c, r);
return Ok((ch, self.cursor));
}
}
}
Err(canvas_expected_characters_not_found(searched.to_vec()))
}
fn search_up(&mut self, layer: usize, searched: &[char], allowed: &[char]) -> Result<(char, Point)> {
let (x, mut y) = self.cursor.unwrap();
while y > 0 {
y -= 1;
let ch = self.content[y][x][layer];
if searched.contains(&ch) {
self.cursor = Point::new(x, y);
return Ok((ch, self.cursor));
}
if !allowed.contains(&ch) {
return Err(canvas_character_is_not_allowed(ch, allowed.to_vec()));
}
}
Err(canvas_expected_characters_not_found(searched.to_vec()))
}
fn search_left(&mut self, layer: usize, searched: &[char], allowed: &[char]) -> Result<(char, Point)> {
let (mut x, y) = self.cursor.unwrap();
while x > 0 {
x -= 1;
let ch = self.content[y][x][layer];
if searched.contains(&ch) {
self.cursor = Point::new(x, y);
return Ok((ch, self.cursor));
}
if !allowed.contains(&ch) {
return Err(canvas_character_is_not_allowed(ch, allowed.to_vec()));
}
}
Err(canvas_expected_characters_not_found(searched.to_vec()))
}
fn search_right(&mut self, layer: usize, searched: &[char], allowed: &[char]) -> Result<(char, Point)> {
let (mut x, y) = self.cursor.unwrap();
while x < self.content[y].len() - 1 {
x += 1;
let ch = self.content[y][x][layer];
if searched.contains(&ch) {
self.cursor = Point::new(x, y);
return Ok((ch, self.cursor));
}
if !allowed.contains(&ch) {
return Err(canvas_character_is_not_allowed(ch, allowed.to_vec()));
}
}
Err(canvas_expected_characters_not_found(searched.to_vec()))
}
fn search_down(&mut self, layer: usize, searched: &[char], allowed: &[char]) -> Result<(char, Point)> {
let (x, mut y) = self.cursor.unwrap();
while y < self.content.len() - 1 {
y += 1;
let ch = self.content[y][x][layer];
if searched.contains(&ch) {
self.cursor = Point::new(x, y);
return Ok((ch, self.cursor));
}
if !allowed.contains(&ch) {
return Err(canvas_character_is_not_allowed(ch, allowed.to_vec()));
}
}
Err(canvas_expected_characters_not_found(searched.to_vec()))
}
fn text_from_rect(&self, layer: usize, r: &Rect) -> String {
let mut text = String::new();
let mut new_line = false;
for row in self.content[(r.top + 1)..(r.bottom - 1)].iter() {
for column in row[(r.left + 1)..(r.right - 1)].iter() {
if new_line {
text.push('\n');
new_line = false;
}
text.push(column[layer]);
}
new_line = true;
}
text
}
fn copy_layer(&mut self, src: Layer, dst: Layer) {
for y in 0..self.content.len() {
for x in 0..self.content[y].len() {
self.content[y][x][dst] = self.content[y][x][src];
}
}
}
pub fn display_text_layer(&self) {
self.display_layer(LAYER_TEXT);
}
pub fn display_thin_layer(&self) {
self.display_layer(LAYER_THIN);
}
pub fn display_body_layer(&self) {
self.display_layer(LAYER_BODY);
}
pub fn display_grid_layer(&self) {
self.display_layer(LAYER_GRID);
}
pub fn display_layer(&self, l: Layer) {
if l < LAYER_COUNT {
println!("{}", LAYER_NAMES[l]);
for row in &self.content {
for layers in row {
print!("{}", layers[l])
}
println!()
}
}
}
}
pub fn scan(text: &str) -> Result<Canvas> {
let mut width: usize = 0;
let mut height: usize = 0;
let mut content = vec![vec![]];
let mut start_adding = false;
let mut end_adding = false;
for line in text.lines() {
let line = line.trim();
if !line.is_empty() {
if line.starts_with(CORNER_TOP_LEFT) && !start_adding && !end_adding {
start_adding = true;
}
if start_adding && !end_adding {
content.push(vec![]);
height += 1;
let mut count = 0;
let mut layers = [CHAR_WHITE; LAYER_COUNT];
for chr in line.chars() {
layers[0] = chr;
content[height - 1].push(layers);
count += 1;
}
if count > width {
width = count;
}
}
if line.ends_with(CORNER_BOTTOM_RIGHT) && start_adding && !end_adding {
end_adding = true;
}
}
}
if height > 0 && width > 0 {
for row in &mut content {
while row.len() < width {
row.push([CHAR_OUTER; LAYER_COUNT]);
}
}
}
let mut canvas = Canvas {
content,
cursor: POINT_ZERO,
cross: None,
cross_horz: None,
cross_vert: None,
information_item_name: None,
body_rect: None,
};
canvas.recognize_information_item_name()?;
canvas.recognize_crossings()?;
canvas.body_rect = Some(canvas.recognize_body_rect()?);
canvas.prepare_regions(LAYER_TEXT, LAYER_THIN);
canvas.remove_information_item_region(LAYER_THIN, LAYER_BODY);
canvas.make_grid(LAYER_BODY, LAYER_GRID);
Ok(canvas)
}