use crate::base::{Point, Rect, Size};
use super::cell::{Cell, Glyph, GlyphPool};
use super::style::Style;
use crate::text;
const DAMAGE_CAP: usize = 32;
const LINK_TABLE_CAP: usize = u16::MAX as usize;
pub struct Surface {
pub(super) size: Size,
pub(super) cells: Vec<Cell>,
pool: GlyphPool,
links: Vec<Box<str>>,
links_dropped: u32,
damage: Vec<Rect>,
}
impl Surface {
pub fn new(size: Size, fill: Cell) -> Surface {
let size = Size::new(size.w.max(0), size.h.max(0));
let mut s = Surface {
size,
cells: vec![Cell::EMPTY; (size.w * size.h).max(0) as usize],
pool: GlyphPool::default(),
links: Vec::new(),
links_dropped: 0,
damage: Vec::new(),
};
if fill != Cell::EMPTY {
s.fill_rect(s.bounds(), fill);
}
s.damage_all();
s
}
pub fn size(&self) -> Size {
self.size
}
pub fn width(&self) -> i32 {
self.size.w
}
pub fn height(&self) -> i32 {
self.size.h
}
pub fn bounds(&self) -> Rect {
Rect::from_size(self.size)
}
pub fn pool(&self) -> &GlyphPool {
&self.pool
}
pub fn register_link(&mut self, uri: &str) -> u16 {
if let Some(i) = self.links.iter().position(|u| &**u == uri) {
return (i + 1) as u16;
}
if self.links.len() >= LINK_TABLE_CAP {
self.links_dropped = self.links_dropped.saturating_add(1);
return 0;
}
self.links.push(uri.into());
self.links.len() as u16
}
pub fn link_uri(&self, id: u16) -> Option<&str> {
if id == 0 {
None
} else {
self.links.get(id as usize - 1).map(|u| &**u)
}
}
pub fn links_dropped(&self) -> u32 {
self.links_dropped
}
pub fn glyph_str<'a>(&'a self, cell: &'a Cell) -> &'a str {
cell.glyph.as_str(&self.pool)
}
pub fn add_damage(&mut self, rect: Rect) {
let rect = rect.intersect(self.bounds());
if rect.is_empty() {
return;
}
if self.damage.len() >= DAMAGE_CAP {
let union = self.damage.drain(..).fold(rect, Rect::union);
self.damage.push(union);
} else {
self.damage.push(rect);
}
}
pub fn damage_all(&mut self) {
self.damage.clear();
self.damage.push(self.bounds());
}
pub fn take_damage(&mut self, out: &mut Vec<Rect>) {
out.append(&mut self.damage);
}
pub fn has_damage(&self) -> bool {
!self.damage.is_empty()
}
fn damage_span(&mut self, y: i32, x0: i32, x1: i32) {
self.add_damage(Rect::new(x0 - 1, y, (x1 - x0) + 2, 1));
}
pub(super) fn idx(&self, x: i32, y: i32) -> usize {
debug_assert!(self.bounds().contains(Point::new(x, y)));
(y * self.size.w + x) as usize
}
pub fn get(&self, x: i32, y: i32) -> Option<&Cell> {
if self.bounds().contains(Point::new(x, y)) {
Some(&self.cells[(y * self.size.w + x) as usize])
} else {
None
}
}
pub(crate) fn row(&self, y: i32) -> &[Cell] {
let start = self.idx(0, y);
&self.cells[start..start + self.size.w as usize]
}
pub fn set(&mut self, x: i32, y: i32, cell: Cell) {
let (x0, x1) = self.set_quiet(x, y, cell);
if x1 > x0 {
self.damage_span(y, x0, x1);
}
}
pub(crate) fn set_quiet(&mut self, x: i32, y: i32, cell: Cell) -> (i32, i32) {
if !self.bounds().contains(Point::new(x, y)) {
return (x, x);
}
if cell.is_continuation() {
self.write_narrow(x, y, cell.blanked());
(x, x + 1)
} else if cell.glyph.width() >= 2 {
if x + 1 < self.size.w {
self.write_wide(x, y, cell);
(x, x + 2)
} else {
self.write_narrow(x, y, cell.blanked());
(x, x + 1)
}
} else {
self.write_narrow(x, y, cell);
(x, x + 1)
}
}
fn write_narrow(&mut self, x: i32, y: i32, cell: Cell) {
self.release_edges(y, x, x + 1);
let i = self.idx(x, y);
self.cells[i] = cell;
}
fn write_wide(&mut self, x: i32, y: i32, leader: Cell) {
self.release_edges(y, x, x + 2);
let i = self.idx(x, y);
self.cells[i] = leader;
self.cells[i + 1] = Cell::continuation_of(&leader);
}
fn release_edges(&mut self, y: i32, x0: i32, x1: i32) {
if x0 > 0 && self.cells[self.idx(x0, y)].is_continuation() {
let i = self.idx(x0 - 1, y);
self.cells[i] = self.cells[i].blanked();
}
if x1 < self.size.w && self.cells[self.idx(x1 - 1, y)].is_wide_leader() {
let i = self.idx(x1, y);
self.cells[i] = self.cells[i].blanked();
}
}
pub(super) fn sever_pairs_at_edges(&mut self, region: Rect) {
for y in region.y..region.bottom() {
if region.x > 0 && self.cells[self.idx(region.x, y)].is_continuation() {
let li = self.idx(region.x - 1, y);
let ci = self.idx(region.x, y);
self.cells[li] = self.cells[li].blanked();
self.cells[ci] = self.cells[ci].blanked();
}
let last = region.right() - 1;
if region.right() < self.size.w && self.cells[self.idx(last, y)].is_wide_leader() {
let li = self.idx(last, y);
let ci = self.idx(last + 1, y);
self.cells[li] = self.cells[li].blanked();
self.cells[ci] = self.cells[ci].blanked();
}
}
}
pub fn fill_rect(&mut self, rect: Rect, cell: Cell) {
let r = rect.intersect(self.bounds());
if r.is_empty() {
return;
}
let cell = sanitize_fill(cell);
let wide = cell.glyph.width() >= 2;
for y in r.y..r.bottom() {
self.release_edges(y, r.x, r.right());
let base = self.idx(r.x, y);
if !wide {
self.cells[base..base + r.w as usize].fill(cell);
} else {
let mut x = 0usize;
while x + 2 <= r.w as usize {
self.cells[base + x] = cell;
self.cells[base + x + 1] = Cell::continuation_of(&cell);
x += 2;
}
if x < r.w as usize {
self.cells[base + x] = cell.blanked();
}
}
self.damage_span(y, r.x, r.right());
}
}
pub fn clear(&mut self, cell: Cell) {
let mut cell = sanitize_fill(cell);
if cell.glyph.is_pooled() {
let s = cell.glyph.as_str(&self.pool).to_string();
let w = cell.glyph.width() as u8;
self.pool.clear();
cell.glyph = Glyph::from_cluster_unchecked(&s, w, &mut self.pool);
} else {
self.pool.clear();
}
self.fill_rect(self.bounds(), cell);
self.damage_all();
}
pub fn draw_text(&mut self, x: i32, y: i32, s: &str, style: Style) -> i32 {
use unicode_segmentation::UnicodeSegmentation;
if y < 0 || y >= self.size.h {
return 0;
}
let mut pen = x;
let mut span: Option<(i32, i32)> = None;
let mut mark = |x0: i32, x1: i32| match &mut span {
Some((lo, hi)) => {
*lo = (*lo).min(x0);
*hi = (*hi).max(x1);
}
None => span = Some((x0, x1)),
};
for cluster in s.graphemes(true) {
let w = text::cluster_width(cluster);
if w <= 0 {
continue;
}
if pen + w > self.size.w {
break; }
if pen + w <= 0 {
pen += w; continue;
}
if pen < 0 {
let base = *self.get(0, y).expect("in bounds");
self.write_narrow(0, y, style.apply(&base).blanked());
mark(0, 1);
pen += w;
continue;
}
let glyph = Glyph::from_cluster_unchecked(cluster, w as u8, &mut self.pool);
let base = *self.get(pen, y).expect("in bounds");
let mut cell = style.apply(&base);
cell.glyph = glyph;
if w == 2 {
self.write_wide(pen, y, cell);
} else {
self.write_narrow(pen, y, cell);
}
mark(pen, pen + w);
pen += w;
}
if let Some((x0, x1)) = span {
self.damage_span(y, x0, x1);
}
pen - x
}
pub fn blit(&mut self, src: &Surface, src_rect: Rect, dst: Point) {
let src_rect = src_rect.intersect(src.bounds());
if src_rect.is_empty() {
return;
}
let dst_rect = Rect::new(dst.x, dst.y, src_rect.w, src_rect.h).intersect(self.bounds());
if dst_rect.is_empty() {
return;
}
let sx = src_rect.x + (dst_rect.x - dst.x);
let sy = src_rect.y + (dst_rect.y - dst.y);
for row in 0..dst_rect.h {
let y = dst_rect.y + row;
self.release_edges(y, dst_rect.x, dst_rect.right());
for col in 0..dst_rect.w {
let cell = src.cells[src.idx(sx + col, sy + row)];
let adopted = self.adopt_cell(cell, src);
let i = self.idx(dst_rect.x + col, y);
self.cells[i] = adopted;
}
let first = self.idx(dst_rect.x, y);
if self.cells[first].is_continuation() {
self.cells[first] = self.cells[first].blanked();
}
let last = self.idx(dst_rect.right() - 1, y);
if self.cells[last].is_wide_leader() {
self.cells[last] = self.cells[last].blanked();
}
self.damage_span(y, dst_rect.x, dst_rect.right());
}
}
pub(crate) fn put_composed(&mut self, x: i32, y: i32, cell: Cell) {
let i = self.idx(x, y);
self.cells[i] = cell;
}
pub(crate) fn repair_wide_pairs(&mut self, y: i32, x0: i32, x1: i32) {
let mut x = (x0 - 1).max(0);
let end = (x1 + 1).min(self.size.w);
if x > 0
&& self.cells[self.idx(x, y)].is_continuation()
&& self.cells[self.idx(x - 1, y)].is_wide_leader()
{
x -= 1;
}
while x < end {
let i = self.idx(x, y);
let cell = self.cells[i];
if cell.is_continuation() {
self.cells[i] = cell.blanked();
x += 1;
} else if cell.is_wide_leader() {
if x + 1 < self.size.w && self.cells[i + 1].is_continuation() {
self.cells[i + 1] = Cell::continuation_of(&cell);
x += 2;
} else {
self.cells[i] = cell.blanked();
x += 1;
}
} else {
x += 1;
}
}
}
pub(crate) fn adopt_from(&mut self, cell: Cell, src: &Surface) -> Cell {
self.adopt_cell(cell, src)
}
fn adopt_cell(&mut self, mut cell: Cell, src: &Surface) -> Cell {
if cell.glyph.is_pooled() {
let s = cell.glyph.as_str(&src.pool);
let width = cell.glyph.width() as u8;
cell.glyph = Glyph::from_cluster_unchecked(s, width, &mut self.pool);
}
if cell.link != 0 {
cell.link = match src.link_uri(cell.link) {
Some(uri) => self.register_link(uri),
None => 0,
};
}
cell
}
}
pub(super) fn sanitize_fill(cell: Cell) -> Cell {
if cell.is_continuation() {
cell.blanked()
} else {
cell
}
}
#[cfg(test)]
#[path = "surface_tests.rs"]
mod tests;