use crate::{CellReader, GlyphCatalog, Pos, Window};
#[must_use]
pub fn render(source: &impl CellReader, glyphs: &GlyphCatalog, window: Window) -> String {
(0..window.size.height)
.map(|dy| {
let row: String = (0..window.size.width)
.map(|dx| glyph_at(source, glyphs, window.origin, dx, dy))
.collect();
row + "\n"
})
.collect()
}
fn glyph_at<'a>(
source: &'a impl CellReader,
glyphs: &'a GlyphCatalog,
origin: Pos,
dx: u16,
dy: u16,
) -> &'a str {
origin
.x
.checked_add_unsigned(dx)
.zip(origin.y.checked_add_unsigned(dy))
.and_then(|(x, y)| source.cell(Pos { x, y }))
.and_then(|cell| cell.glyph_str(glyphs))
.unwrap_or(" ")
}
#[cfg(test)]
mod tests {
use super::render;
use crate::{
Arm, Buffer, Cell, Glyph, GlyphCatalog, Pos, Size, StampMode, StampSink, Stroke,
StrokeCell, Window,
};
fn light() -> Stroke {
Stroke::from("light")
}
#[test]
fn a_single_junction_renders_as_a_cross() {
let mut buffer = Buffer::new(Window {
origin: Pos { x: 0, y: 0 },
size: Size {
width: 1,
height: 1,
},
});
buffer.stamp(
Pos { x: 0, y: 0 },
StrokeCell {
base: light(),
top: Arm::Set(light()),
right: Arm::Set(light()),
bottom: Arm::Set(light()),
left: Arm::Set(light()),
}
.into(),
StampMode::Above,
None,
);
let text = render(&buffer, &GlyphCatalog::light(), buffer.window());
assert_eq!(text, "┼\n");
}
#[test]
fn a_box_renders_from_eight_stamps() {
let mut buffer = Buffer::new(Window {
origin: Pos { x: 0, y: 0 },
size: Size {
width: 4,
height: 3,
},
});
let corner = |top, right, bottom, left| -> Cell {
StrokeCell {
base: light(),
top,
right,
bottom,
left,
}
.into()
};
let set = || Arm::Set(light());
buffer.stamp(
Pos { x: 0, y: 0 },
corner(Arm::Closed, set(), set(), Arm::Closed),
StampMode::Above,
None,
);
buffer.stamp(
Pos { x: 1, y: 0 },
corner(Arm::Closed, set(), Arm::Closed, set()),
StampMode::Above,
None,
);
buffer.stamp(
Pos { x: 2, y: 0 },
corner(Arm::Closed, set(), Arm::Closed, set()),
StampMode::Above,
None,
);
buffer.stamp(
Pos { x: 3, y: 0 },
corner(Arm::Closed, Arm::Closed, set(), set()),
StampMode::Above,
None,
);
buffer.stamp(
Pos { x: 0, y: 1 },
corner(set(), Arm::Closed, set(), Arm::Closed),
StampMode::Above,
None,
);
buffer.stamp(
Pos { x: 3, y: 1 },
corner(set(), Arm::Closed, set(), Arm::Closed),
StampMode::Above,
None,
);
buffer.stamp(
Pos { x: 0, y: 2 },
corner(set(), set(), Arm::Closed, Arm::Closed),
StampMode::Above,
None,
);
buffer.stamp(
Pos { x: 1, y: 2 },
corner(Arm::Closed, set(), Arm::Closed, set()),
StampMode::Above,
None,
);
buffer.stamp(
Pos { x: 2, y: 2 },
corner(Arm::Closed, set(), Arm::Closed, set()),
StampMode::Above,
None,
);
buffer.stamp(
Pos { x: 3, y: 2 },
corner(set(), Arm::Closed, Arm::Closed, set()),
StampMode::Above,
None,
);
let text = render(&buffer, &GlyphCatalog::light(), buffer.window());
assert_eq!(text, "┌──┐\n│ │\n└──┘\n");
}
#[test]
fn an_abstaining_stamp_renders_the_surviving_arm() {
let mut buffer = Buffer::new(Window {
origin: Pos { x: 0, y: 0 },
size: Size {
width: 1,
height: 1,
},
});
buffer.stamp(
Pos { x: 0, y: 0 },
StrokeCell {
base: light(),
top: Arm::Set(light()),
right: Arm::Closed,
bottom: Arm::Closed,
left: Arm::Closed,
}
.into(),
StampMode::Above,
None,
);
buffer.stamp(
Pos { x: 0, y: 0 },
StrokeCell {
base: light(),
top: Arm::Unset,
right: Arm::Set(light()),
bottom: Arm::Closed,
left: Arm::Set(light()),
}
.into(),
StampMode::Above,
None,
);
let text = render(&buffer, &GlyphCatalog::light(), buffer.window());
assert_eq!(text, "┴\n");
}
#[test]
fn a_key_the_catalog_does_not_answer_renders_as_a_space() {
let mut buffer = Buffer::new(Window {
origin: Pos { x: 0, y: 0 },
size: Size {
width: 1,
height: 1,
},
});
buffer.stamp(
Pos { x: 0, y: 0 },
StrokeCell {
base: Stroke::from("double"),
top: Arm::Set(Stroke::from("double")),
right: Arm::Closed,
bottom: Arm::Closed,
left: Arm::Closed,
}
.into(),
StampMode::Above,
None,
);
let text = render(&buffer, &GlyphCatalog::light(), buffer.window());
assert_eq!(text, " \n");
}
#[test]
fn an_undefined_cell_and_a_fully_closed_one_both_render_as_a_space() {
let mut buffer = Buffer::new(Window {
origin: Pos { x: 0, y: 0 },
size: Size {
width: 2,
height: 1,
},
});
buffer.stamp(
Pos { x: 0, y: 0 },
StrokeCell {
base: light(),
top: Arm::Closed,
right: Arm::Closed,
bottom: Arm::Closed,
left: Arm::Closed,
}
.into(),
StampMode::Above,
None,
);
let text = render(&buffer, &GlyphCatalog::light(), buffer.window());
assert_eq!(text, " \n");
}
#[test]
fn a_buffer_with_no_positions_renders_as_spaces() {
let mut buffer = Buffer::new(Window {
origin: Pos { x: 0, y: 0 },
size: Size {
width: 0,
height: 0,
},
});
buffer.stamp(
Pos { x: 0, y: 0 },
StrokeCell {
base: light(),
top: Arm::Set(light()),
right: Arm::Set(light()),
bottom: Arm::Set(light()),
left: Arm::Set(light()),
}
.into(),
StampMode::Above,
None,
);
let text = render(
&buffer,
&GlyphCatalog::light(),
Window {
origin: Pos { x: 0, y: 0 },
size: Size {
width: 2,
height: 1,
},
},
);
assert_eq!(text, " \n");
}
#[test]
fn an_origin_near_i16_max_does_not_overflow() {
let origin = Pos {
x: i16::MAX - 1,
y: 0,
};
let size = Size {
width: 3,
height: 1,
};
let buffer = Buffer::new(Window { origin, size });
let text = render(&buffer, &GlyphCatalog::light(), buffer.window());
assert_eq!(text, " \n");
}
#[test]
fn rendering_outside_the_window_fills_it_with_spaces() {
let mut buffer = Buffer::new(Window {
origin: Pos { x: 0, y: 0 },
size: Size {
width: 2,
height: 1,
},
});
buffer.stamp(
Pos { x: 0, y: 0 },
StrokeCell {
base: light(),
top: Arm::Set(light()),
right: Arm::Closed,
bottom: Arm::Set(light()),
left: Arm::Closed,
}
.into(),
StampMode::Above,
None,
);
let text = render(
&buffer,
&GlyphCatalog::light(),
Window {
origin: Pos { x: -1, y: 0 },
size: Size {
width: 4,
height: 1,
},
},
);
assert_eq!(text, " │ \n");
}
#[test]
fn a_literal_renders_as_itself_with_a_catalog_that_has_no_rule_for_it() {
let mut buffer = Buffer::new(Window {
origin: Pos { x: 0, y: 0 },
size: Size {
width: 1,
height: 1,
},
});
buffer.stamp(
Pos { x: 0, y: 0 },
Cell::Literal(Glyph::new("A").expect("\"A\" is one glyph")),
StampMode::Above,
None,
);
let text = render(&buffer, &GlyphCatalog::light(), buffer.window());
assert_eq!(text, "A\n");
}
}