surf_n_term/
render.rs

1//! Terminal rendering logic
2use crate::{
3    Face, Glyph, Image, ImageHandler, KittyImageHandler, Position, Size, Surface, SurfaceMut,
4    SurfaceMutView, SurfaceOwned, SurfaceView, Terminal, TerminalCaps, TerminalCommand,
5    TerminalEvent, TerminalSize, TerminalWaker,
6    decoder::{Decoder, TTYCommandDecoder, Utf8Decoder},
7    encoder::{Encoder, TTYEncoder},
8    error::Error,
9    view::{
10        BoxConstraint, IntoView, Layout, Text, Tree, TreeId, TreeMut, View, ViewContext,
11        ViewLayoutStore, ViewMutLayout,
12    },
13};
14use rasterize::RGBA;
15use std::{
16    cmp::{max, min},
17    collections::HashMap,
18    fmt::Debug,
19    io::{BufWriter, Write},
20};
21use unicode_width::UnicodeWidthChar;
22
23/// Terminal cell kind
24#[derive(Debug, Clone, PartialEq, Eq, Hash)]
25pub enum CellKind {
26    /// Contains character
27    Char(char),
28    /// Contains image
29    Image(Image),
30    /// Contains glyph
31    Glyph(Glyph),
32}
33
34/// Terminal cell
35#[derive(Debug, Clone, PartialEq, Eq, Hash)]
36pub struct Cell {
37    /// Cell's face
38    face: Face,
39    /// Cell's kind
40    kind: CellKind,
41}
42
43impl Cell {
44    /// Create new cell from face and char
45    pub fn new_char(face: Face, character: char) -> Self {
46        Self {
47            face,
48            kind: CellKind::Char(character),
49        }
50    }
51
52    /// Create new cell from image
53    pub fn new_image(image: Image) -> Self {
54        Self {
55            face: Default::default(),
56            kind: CellKind::Image(image),
57        }
58    }
59
60    /// Create new cell from glyph
61    pub fn new_glyph(face: Face, glyph: Glyph) -> Self {
62        Self {
63            face,
64            kind: CellKind::Glyph(glyph),
65        }
66    }
67
68    /// Get size of the cell
69    pub fn size(&self, ctx: &ViewContext) -> Size {
70        match &self.kind {
71            CellKind::Char(ch) => Size::new(1, ch.width().unwrap_or(0)),
72            CellKind::Glyph(glyph) => {
73                if ctx.has_glyphs() {
74                    glyph.size()
75                } else {
76                    Size {
77                        height: 1,
78                        width: glyph
79                            .fallback_str()
80                            .chars()
81                            .map(|c| c.width().unwrap_or(0))
82                            .sum(),
83                    }
84                }
85            }
86            CellKind::Image(image) => image.size_cells(ctx.pixels_per_cell()),
87        }
88    }
89
90    /// Cell face
91    pub fn face(&self) -> Face {
92        self.face
93    }
94
95    /// Replace cell face
96    pub fn with_face(self, face: Face) -> Self {
97        Cell { face, ..self }
98    }
99
100    /// Return cell kind
101    pub fn kind(&self) -> &CellKind {
102        &self.kind
103    }
104
105    pub fn overlay(&mut self, other: Cell) -> &mut Self {
106        self.face = self.face.overlay(&other.face);
107        self.kind = other.kind;
108        self
109    }
110
111    /// Layout cell
112    ///
113    /// Arguments:
114    ///   - `max_width`       - maximum available width
115    ///   - `pixels_per_cell` - number of pixels in a cell
116    ///   - `size`            - tracked total size
117    ///   - `cursor`          - tracked current cursor position
118    ///
119    /// Returns optional position where cell needs to be placed.
120    pub fn layout(
121        &self,
122        ctx: &ViewContext,
123        max_width: usize,
124        wraps: bool,
125        size: &mut Size,
126        cursor: &mut Position,
127    ) -> Option<Position> {
128        // special characters
129        if let CellKind::Char(character) = &self.kind {
130            match character {
131                '\n' => {
132                    size.width = max(size.width, cursor.col);
133                    size.height = max(size.height, cursor.row + 1);
134                    cursor.col = 0;
135                    cursor.row += 1;
136                    return None;
137                }
138                '\r' => {
139                    cursor.col = 0;
140                    return None;
141                }
142                '\t' => {
143                    cursor.col += (8 - cursor.col % 8).min(max_width.saturating_sub(cursor.col));
144                    size.width = max(size.width, cursor.col);
145                    return None;
146                }
147                _ => {}
148            }
149        }
150
151        // skip empty cells
152        let cell_size = self.size(ctx);
153        if cell_size.height == 0 || cell_size.width == 0 {
154            return None;
155        }
156
157        if cursor.col + cell_size.width <= max_width {
158            // enough space to put cell
159            let pos = *cursor;
160            cursor.col += cell_size.width;
161            size.width = max(size.width, cursor.col);
162            size.height = max(size.height, cursor.row + cell_size.height);
163            Some(pos)
164        } else if !wraps {
165            None
166        } else {
167            // put new line
168            cursor.row += 1;
169            cursor.col = 0;
170            size.height = max(size.height, cursor.row);
171            // put cell unconditionally
172            let pos = *cursor;
173            cursor.col = min(cell_size.width, max_width);
174            size.width = max(size.width, cursor.col);
175            size.height = max(size.height, cursor.row + cell_size.height);
176            Some(pos)
177        }
178    }
179}
180
181impl Default for Cell {
182    fn default() -> Self {
183        Self::new_char(Face::default(), ' ')
184    }
185}
186
187#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
188enum CellMark {
189    /// Not marked
190    #[default]
191    Empty,
192    /// Needs to be ignored
193    Ignored,
194    /// Needs to be returned during diffing
195    Damaged,
196}
197
198pub type TerminalSurface<'a> = SurfaceMutView<'a, Cell>;
199
200/// Terminal renderer
201///
202/// This object keeps two surfaces front (new) and back (old) and on each call
203/// to frame function it generates necessary terminal commands to reconcile them.
204pub struct TerminalRenderer {
205    /// Current terminal size (not changes for the lifetime of the object)
206    size: TerminalSize,
207    /// Front surface (new)
208    front: SurfaceOwned<Cell>,
209    /// Back surface (old)
210    back: SurfaceOwned<Cell>,
211
212    /// Marked cell that are treaded specially during diffing
213    marks: SurfaceOwned<CellMark>,
214    /// Images to be rendered (frame function local, kept here to avoid allocation)
215    images: Vec<(Position, Face, Image)>,
216
217    /// Cache of rendered glyphs
218    glyph_cache: HashMap<Cell, Image>,
219    /// Frame counter
220    frame_count: usize,
221}
222
223impl TerminalRenderer {
224    /// Create new terminal renderer
225    pub fn new<T: Terminal + ?Sized>(term: &mut T, clear: bool) -> Result<Self, Error> {
226        let size = term.size()?;
227        let mark = if clear {
228            CellMark::Damaged
229        } else {
230            CellMark::Empty
231        };
232        Ok(Self {
233            size,
234            front: SurfaceOwned::new(size.cells),
235            back: SurfaceOwned::new(size.cells),
236            marks: SurfaceOwned::new_with(size.cells, |_| mark),
237            images: Vec::new(),
238            glyph_cache: HashMap::new(),
239            frame_count: 0,
240        })
241    }
242
243    /// Clear terminal
244    pub fn clear<T: Terminal + ?Sized>(&mut self, term: &mut T) -> Result<(), Error> {
245        // erase all images
246        for (pos, cell) in self.back.iter().with_position() {
247            if let CellKind::Image(img) = &cell.kind {
248                term.execute(TerminalCommand::ImageErase(img.clone(), Some(pos)))?;
249            }
250        }
251
252        self.marks.fill(CellMark::Damaged);
253        self.front.fill(Cell::default());
254        self.back.fill(Cell::default());
255
256        Ok(())
257    }
258
259    /// Terminal surface for the new frame
260    pub fn surface(&mut self) -> TerminalSurface<'_> {
261        self.front.as_mut()
262    }
263
264    /// Generate frame, that is issue terminal command to reconcile
265    /// back (old) and front (new) buffers.
266    #[tracing::instrument(name = "[TerminalRenderer.frame]", level="debug", skip_all, fields(frame_count = %self.frame_count))]
267    pub fn frame<T: Terminal + ?Sized>(&mut self, term: &mut T) -> Result<(), Error> {
268        // clear hoisted locals
269        self.images.clear();
270        self.marks.fill(CellMark::Empty);
271
272        // First pass
273        //
274        // - Replace glyphs with images in the front buffer
275        // - Erase changed images
276        // - Record images that we need to render
277        for ((pos, old), new) in self.back.iter().with_position().zip(self.front.iter_mut()) {
278            // replace glyphs with images
279            if let CellKind::Glyph(glyph) = &new.kind {
280                let image = match self.glyph_cache.get(new) {
281                    Some(image) => image.clone(),
282                    None => {
283                        let image = glyph.rasterize(new.face, self.size);
284                        self.glyph_cache.insert(new.clone(), image.clone());
285                        image
286                    }
287                };
288                new.kind = CellKind::Image(image);
289            }
290
291            // skip cells that have not changed, go over ignored items too as they
292            // might remove old images.
293            if old == new && self.marks.get(pos) != Some(&CellMark::Damaged) {
294                // cell under the image needs to be marked as ignored
295                if let CellKind::Image(image) = &new.kind {
296                    let size = image.size_cells(self.size.pixels_per_cell());
297                    self.marks
298                        .view_mut(
299                            pos.row..pos.row + size.height,
300                            pos.col..pos.col + size.width,
301                        )
302                        .fill(CellMark::Ignored);
303                }
304                continue;
305            }
306
307            // erase and damage area under old image
308            if let CellKind::Image(image) = &old.kind {
309                term.execute(TerminalCommand::ImageErase(image.clone(), Some(pos)))?;
310                let size = image.size_cells(self.size.pixels_per_cell());
311                self.marks
312                    .view_mut(
313                        pos.row..pos.row + size.height,
314                        pos.col..pos.col + size.width,
315                    )
316                    .fill(CellMark::Damaged);
317            }
318
319            // record image to be rendered, and mark area under the image to be ignored
320            if let CellKind::Image(image) = &new.kind {
321                self.images.push((pos, new.face, image.clone()));
322                let size = image.size_cells(self.size.pixels_per_cell());
323                self.marks
324                    .view_mut(
325                        pos.row..pos.row + size.height,
326                        pos.col..pos.col + size.width,
327                    )
328                    .fill(CellMark::Ignored);
329            }
330        }
331
332        // Second pass
333        //
334        // Render or characters
335        let mut face = Face::default().with_bg(Some(RGBA::new(1, 2, 3, 255)));
336        let mut cursor = Position::new(123_456, 654_123);
337
338        let mut pos = Position::origin();
339        while pos.row < self.front.height() {
340            while pos.col < self.front.width() {
341                // fetch buffers
342                let offset = self.front.shape().offset(pos);
343                let new = &self.front.data()[offset];
344                let old = &self.back.data()[offset];
345                let mark = self.marks.data()[offset];
346
347                // skip conditions
348                if mark != CellMark::Damaged && (mark == CellMark::Ignored || old == new) {
349                    pos.col += 1;
350                    continue;
351                }
352                let CellKind::Char(character) = &new.kind else {
353                    pos.col += 1;
354                    continue;
355                };
356                let character_width = character.width().unwrap_or(0);
357                if character_width == 0 {
358                    pos.col += 1;
359                    continue;
360                }
361
362                // update face and cursor
363                if face != new.face {
364                    face = new.face;
365                    term.execute(TerminalCommand::Face(face))?;
366                }
367                if cursor != pos {
368                    cursor = pos;
369                    term.execute(TerminalCommand::CursorTo(cursor))?;
370                }
371
372                if matches!(character, ' ') {
373                    // find repeated empty cells
374                    let mut repeats = 1;
375                    for col in pos.col + 1..self.front.width() {
376                        let pos = Position::new(pos.row, col);
377                        let Some(next) = self.front.get(pos) else {
378                            break;
379                        };
380                        let next_mark = self.marks.get(pos).copied().unwrap_or_default();
381                        if next == new && next_mark != CellMark::Ignored {
382                            repeats += 1;
383                        } else {
384                            break;
385                        }
386                    }
387                    pos.col += repeats;
388                    // erase if it is more efficient
389                    if repeats > 4 {
390                        // NOTE: erase is not moving cursor
391                        term.execute(TerminalCommand::EraseChars(repeats))?;
392                    } else {
393                        cursor.col += repeats;
394                        for _ in 0..repeats {
395                            term.execute(TerminalCommand::Char(' '))?;
396                        }
397                    }
398                } else {
399                    term.execute(TerminalCommand::Char(*character))?;
400                    cursor.col += character_width;
401                    pos.col += character_width;
402                }
403            }
404            pos.col = 0;
405            pos.row += 1;
406        }
407
408        // Render images
409        for (pos, face, image) in self.images.drain(..) {
410            // Erase area under image, which makes sure are under image
411            // has the same face as image cells.
412            term.execute(TerminalCommand::Face(face))?;
413            let size = image.size_cells(self.size.pixels_per_cell());
414            for row in pos.row..pos.row + size.height {
415                term.execute(TerminalCommand::CursorTo(Position::new(row, pos.col)))?;
416                term.execute(TerminalCommand::EraseChars(size.width))?;
417            }
418
419            // draw image
420            term.execute(TerminalCommand::CursorTo(pos))?;
421            term.execute(TerminalCommand::Image(image, pos))?;
422        }
423
424        // Flip and clear buffers
425        self.frame_count += 1;
426        std::mem::swap(&mut self.front, &mut self.back);
427        self.front.clear();
428
429        Ok(())
430    }
431}
432
433pub trait CellWrite {
434    /// Get current face
435    fn face(&self) -> Face;
436
437    /// Set current face, returns previous face
438    fn set_face(&mut self, face: Face) -> Face;
439
440    /// Return self with updated face
441    fn with_face(mut self, face: Face) -> Self
442    where
443        Self: Sized,
444    {
445        self.set_face(face);
446        self
447    }
448
449    /// Get wraps flag
450    fn wraps(&self) -> bool;
451
452    /// Set wraps flag, returns previous value
453    fn set_wraps(&mut self, wraps: bool) -> bool;
454
455    /// Return self with updated wraps flag
456    fn with_wraps(mut self, wraps: bool) -> Self
457    where
458        Self: Sized,
459    {
460        self.set_wraps(wraps);
461        self
462    }
463
464    /// Create scope that reverts face and wraps flags on exit
465    fn scope(&mut self, scope: impl FnOnce(&mut Self)) -> &mut Self {
466        let face = self.face();
467        let wraps = self.wraps();
468        scope(self);
469        self.set_wraps(wraps);
470        self.set_face(face);
471        self
472    }
473
474    /// Put cell
475    ///
476    /// Returns `false` when all further puts will be ignored (out of space)
477    fn put_cell(&mut self, cell: Cell) -> bool;
478
479    fn with_cell(mut self, cell: Cell) -> Self
480    where
481        Self: Sized,
482    {
483        self.put_cell(cell);
484        self
485    }
486
487    /// Put character
488    fn put_char(&mut self, character: char) -> bool {
489        self.put_cell(Cell::new_char(self.face(), character))
490    }
491
492    fn with_char(mut self, character: char) -> Self
493    where
494        Self: Sized,
495    {
496        self.put_char(character);
497        self
498    }
499
500    /// Put glyph
501    fn put_glyph(&mut self, glyph: Glyph) -> bool {
502        self.put_cell(Cell::new_glyph(self.face(), glyph))
503    }
504
505    fn with_glyph(mut self, glyph: Glyph) -> Self
506    where
507        Self: Sized,
508    {
509        self.put_glyph(glyph);
510        self
511    }
512
513    /// Put image
514    fn put_image(&mut self, image: Image) -> bool {
515        self.put_cell(Cell::new_image(image))
516    }
517
518    fn with_image(mut self, image: Image) -> Self
519    where
520        Self: Sized,
521    {
522        self.put_image(image);
523        self
524    }
525
526    /// Put anything that is format-able (Note: that [std::format_args] is also [std::fmt::Display])
527    fn put_fmt<T>(&mut self, value: &T, mut face: Option<Face>) -> &mut Self
528    where
529        T: std::fmt::Display + ?Sized,
530    {
531        face = face.map(|face| self.set_face(face));
532        let _ = write!(self.utf8_writer(), "{}", value);
533        face.map(|face| self.set_face(face));
534        self
535    }
536
537    fn with_fmt<T>(mut self, value: &T, face: Option<Face>) -> Self
538    where
539        T: std::fmt::Display + ?Sized,
540        Self: Sized,
541    {
542        self.put_fmt(value, face);
543        self
544    }
545
546    /// Put [Text]
547    fn put_text(&mut self, text: &Text) -> &mut Self {
548        text.cells().iter().cloned().for_each(|cell| {
549            self.put_cell(cell);
550        });
551        self
552    }
553
554    fn with_text(mut self, text: &Text) -> Self
555    where
556        Self: Sized,
557    {
558        self.put_text(text);
559        self
560    }
561
562    /// Returns [std::io::Write] that can decode UTF8 characters
563    ///
564    /// Use together with `.by_ref` for no-owning version
565    fn utf8_writer(self) -> Utf8CellWriter<Self>
566    where
567        Self: Sized,
568    {
569        Utf8CellWriter {
570            parent: self,
571            decoder: Utf8Decoder::new(),
572        }
573    }
574
575    /// Returns [std::io::Write] that can decode TTY escape sequences
576    ///
577    /// Use together with `.by_ref` for no-owning version
578    fn tty_writer(self) -> TTYCellWriter<Self>
579    where
580        Self: Sized,
581    {
582        TTYCellWriter {
583            parent: self,
584            decoder: TTYCommandDecoder::new(),
585        }
586    }
587
588    /// Create a reference of self
589    fn by_ref(&mut self) -> &mut Self
590    where
591        Self: Sized,
592    {
593        self
594    }
595}
596
597impl<W: CellWrite + ?Sized> CellWrite for &mut W {
598    fn face(&self) -> Face {
599        (**self).face()
600    }
601
602    fn set_face(&mut self, face: Face) -> Face {
603        (**self).set_face(face)
604    }
605
606    fn wraps(&self) -> bool {
607        (**self).wraps()
608    }
609
610    fn set_wraps(&mut self, wraps: bool) -> bool {
611        (**self).set_wraps(wraps)
612    }
613
614    fn put_cell(&mut self, cell: Cell) -> bool {
615        (**self).put_cell(cell)
616    }
617}
618
619pub struct Utf8CellWriter<W> {
620    parent: W,
621    decoder: Utf8Decoder,
622}
623
624impl<W> Utf8CellWriter<W> {
625    pub fn parent(&mut self) -> &mut W {
626        &mut self.parent
627    }
628}
629
630impl<W> std::io::Write for Utf8CellWriter<W>
631where
632    W: CellWrite,
633{
634    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
635        let mut cur = std::io::Cursor::new(buf);
636        while let Some(ch) = self.decoder.decode(&mut cur)? {
637            if !self.parent.put_char(ch) {
638                return Ok(buf.len());
639            }
640        }
641        Ok(cur.position() as usize)
642    }
643
644    fn flush(&mut self) -> std::io::Result<()> {
645        Ok(())
646    }
647}
648
649pub struct TTYCellWriter<W> {
650    parent: W,
651    decoder: TTYCommandDecoder,
652}
653
654impl<W> TTYCellWriter<W> {
655    pub fn parent(&mut self) -> &mut W {
656        &mut self.parent
657    }
658}
659
660impl<W> std::io::Write for TTYCellWriter<W>
661where
662    W: CellWrite,
663{
664    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
665        let mut cur = std::io::Cursor::new(buf);
666        while let Some(cmd) = self
667            .decoder
668            .decode(&mut cur)
669            .map_err(std::io::Error::other)?
670        {
671            match cmd {
672                TerminalCommand::Char(c) => {
673                    self.parent.put_char(c);
674                }
675                TerminalCommand::FaceModify(face_modify) => {
676                    self.parent.set_face(face_modify.apply(self.parent.face()));
677                }
678                TerminalCommand::Image(image, _) => {
679                    self.parent.put_image(image);
680                }
681                _ => continue,
682            }
683        }
684        Ok(cur.position() as usize)
685    }
686
687    fn flush(&mut self) -> std::io::Result<()> {
688        Ok(())
689    }
690}
691
692/// Terminal surface extension trait
693pub trait TerminalSurfaceExt: SurfaceMut<Item = Cell> {
694    /// Draw box
695    fn draw_box(&mut self, face: Option<Face>);
696
697    /// Fill surface with check pattern
698    fn draw_check_pattern(&mut self, face: Face);
699
700    /// Draw view on the surface
701    fn draw_view(
702        &mut self,
703        ctx: &ViewContext,
704        layout_store: Option<&mut ViewLayoutStore>,
705        view: impl IntoView,
706    ) -> Result<TreeId, Error>;
707
708    /// Erase surface with face
709    fn erase(&mut self, face: Face);
710
711    /// Write object that can be used to add text to the surface
712    fn writer(&mut self, ctx: &ViewContext) -> TerminalWriter<'_>;
713
714    /// Wrapper around terminal surface that implements [std::fmt::Debug]
715    /// which renders a surface to the terminal.
716    fn debug(&self) -> TerminalSurfaceDebug<'_>;
717}
718
719impl<S> TerminalSurfaceExt for S
720where
721    S: SurfaceMut<Item = Cell>,
722{
723    // Draw square box on the surface
724    fn draw_box(&mut self, face: Option<Face>) {
725        if self.width() < 2 || self.height() < 2 {
726            return;
727        }
728        let face = face.unwrap_or_default();
729
730        let h = Cell::new_char(face, '─');
731        let v = Cell::new_char(face, '│');
732        self.view_mut(0, 1..-1).fill(h.clone());
733        self.view_mut(-1, 1..-1).fill(h);
734        self.view_mut(1..-1, 0).fill(v.clone());
735        self.view_mut(1..-1, -1).fill(v);
736
737        self.view_mut(0, 0).fill(Cell::new_char(face, '┌'));
738        self.view_mut(0, -1).fill(Cell::new_char(face, '┐'));
739        self.view_mut(-1, -1).fill(Cell::new_char(face, '┘'));
740        self.view_mut(-1, 0).fill(Cell::new_char(face, '└'));
741    }
742
743    /// Fill surface with check pattern
744    fn draw_check_pattern(&mut self, face: Face) {
745        self.fill_with(|pos, _| {
746            Cell::new_char(
747                face,
748                if pos.col & 1 == 1 {
749                    '\u{2580}' // upper half block
750                } else {
751                    '\u{2584}' // lower half block
752                },
753            )
754        })
755    }
756
757    /// Draw view on the surface
758    fn draw_view(
759        &mut self,
760        ctx: &ViewContext,
761        layout_store: Option<&mut ViewLayoutStore>,
762        view: impl IntoView,
763    ) -> Result<TreeId, Error> {
764        let view = view.into_view();
765
766        let mut layout_store_vec = ViewLayoutStore::new();
767        let layout_store = layout_store.unwrap_or(&mut layout_store_vec);
768        layout_store.clear();
769
770        let mut layout = ViewMutLayout::new(layout_store, Layout::default());
771
772        view.layout(ctx, BoxConstraint::loose(self.size()), layout.view_mut())?;
773        view.render(ctx, self.as_mut(), layout.view())?;
774        Ok(layout.id())
775    }
776
777    /// Replace all cells with empty character and provided face
778    fn erase(&mut self, face: Face) {
779        self.fill_with(|_, cell| Cell::new_char(cell.face.overlay(&face), ' '));
780    }
781
782    /// Crete writable wrapper around the terminal surface
783    fn writer(&mut self, ctx: &ViewContext) -> TerminalWriter<'_> {
784        TerminalWriter::new(ctx.clone(), self)
785    }
786
787    /// Create object that implements [Debug], only useful in tests
788    /// and for debugging
789    fn debug(&self) -> TerminalSurfaceDebug<'_> {
790        TerminalSurfaceDebug {
791            surf: self.as_ref(),
792        }
793    }
794}
795
796/// Writable (implements `Write`) object for `TerminalSurface`
797pub struct TerminalWriter<'a> {
798    ctx: ViewContext,
799    wraps: bool,
800    face: Face,       // face underlay-ed over all cells
801    cursor: Position, // cursor position (next insert will happen at this position)
802    size: Size,       // actual used size
803    surf: SurfaceMutView<'a, Cell>,
804    decoder: crate::decoder::Utf8Decoder,
805}
806
807impl<'a> TerminalWriter<'a> {
808    pub fn new<S>(ctx: ViewContext, surf: &'a mut S) -> Self
809    where
810        S: SurfaceMut<Item = Cell> + ?Sized,
811    {
812        Self {
813            ctx,
814            wraps: true,
815            face: Default::default(),
816            cursor: Position::origin(),
817            size: Size::empty(),
818            surf: surf.as_mut(),
819            decoder: crate::decoder::Utf8Decoder::new(),
820        }
821    }
822
823    /// Get current cursor position
824    pub fn cursor(&self) -> Position {
825        self.cursor
826    }
827
828    /// Move cursor to specified position
829    pub fn set_cursor(&mut self, pos: Position) -> &mut Self {
830        self.cursor = Position {
831            col: min(pos.col, self.size().width),
832            row: min(pos.row, self.size().height),
833        };
834        self
835    }
836
837    /// Get size of the view backing this writer
838    pub fn size(&self) -> Size {
839        self.surf.size()
840    }
841}
842
843impl CellWrite for TerminalWriter<'_> {
844    fn face(&self) -> Face {
845        self.face
846    }
847
848    fn set_face(&mut self, face: Face) -> Face {
849        std::mem::replace(&mut self.face, face)
850    }
851
852    fn wraps(&self) -> bool {
853        self.wraps
854    }
855
856    fn set_wraps(&mut self, wraps: bool) -> bool {
857        std::mem::replace(&mut self.wraps, wraps)
858    }
859
860    fn put_cell(&mut self, cell: Cell) -> bool {
861        if !self.ctx.has_glyphs() {
862            // glyph string fallback
863            if let CellKind::Glyph(glyph) = &cell.kind {
864                return glyph
865                    .fallback_str()
866                    .chars()
867                    .all(|c| self.put_cell(Cell::new_char(cell.face, c)));
868            }
869        }
870
871        let face = self.face.overlay(&cell.face);
872        let cursor_start = self.cursor;
873        if let Some(pos) = cell.layout(
874            &self.ctx,
875            self.size().width,
876            self.wraps,
877            &mut self.size,
878            &mut self.cursor,
879        ) {
880            // normal cell
881            if let Some(cell_ref) = self.surf.get_mut(pos) {
882                cell_ref.overlay(cell.with_face(face));
883                true
884            } else {
885                false
886            }
887        } else if cursor_start != self.cursor {
888            // cursor has been moved by special character, and we want to fill
889            // skipped cells with current face
890            let shape = self.surf.shape();
891            let data = self.surf.data_mut();
892
893            let start = shape.offset(cursor_start);
894            let end = shape.offset(self.cursor);
895
896            for row in cursor_start.row..min(self.cursor.row + 1, shape.height) {
897                for col in 0..shape.width {
898                    let offset = shape.offset(Position::new(row, col));
899                    if (start..end).contains(&offset) {
900                        let cell = &mut data[offset];
901                        cell.face = cell.face.overlay(&face);
902                    }
903                }
904            }
905            true
906        } else {
907            true
908        }
909    }
910}
911
912impl std::io::Write for TerminalWriter<'_> {
913    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
914        let mut cur = std::io::Cursor::new(buf);
915        while let Some(ch) = self.decoder.decode(&mut cur)? {
916            if !self.put_char(ch) {
917                return Ok(buf.len());
918            }
919        }
920        Ok(cur.position() as usize)
921    }
922
923    fn flush(&mut self) -> std::io::Result<()> {
924        Ok(())
925    }
926}
927
928pub struct TerminalSurfaceDebug<'a> {
929    surf: SurfaceView<'a, Cell>,
930}
931
932impl TerminalSurfaceDebug<'_> {
933    /// Write rendered surface to the output
934    pub fn save<W: Write + Send>(&self, output: W) -> Result<W, Error> {
935        // init capabilities
936        let capabilities = TerminalCaps {
937            depth: crate::encoder::ColorDepth::TrueColor,
938            glyphs: true,
939            kitty_keyboard: false,
940        };
941
942        // init size
943        let size = Size {
944            width: self.surf.width() + 2,
945            height: self.surf.height() + 2,
946        };
947
948        // init debug terminal
949        let ctx = ViewContext::dummy();
950        let pixels_per_cell = ctx.pixels_per_cell();
951        let mut term = TerminalDebug {
952            size: TerminalSize {
953                cells: size,
954                pixels: Size {
955                    height: pixels_per_cell.height * size.height,
956                    width: pixels_per_cell.width * size.width,
957                },
958            },
959            encoder: TTYEncoder::new(capabilities.clone()),
960            image_handler: KittyImageHandler::new().quiet(),
961            output,
962            capabilities,
963            face: Default::default(),
964        };
965
966        // move to origin an save cursor position
967        let scroll = size.height as i32;
968        term.execute_many([
969            TerminalCommand::Scroll(scroll),
970            TerminalCommand::CursorMove {
971                row: -scroll,
972                col: 0,
973            },
974            TerminalCommand::CursorSave,
975        ])?;
976
977        // create renderer
978        let mut renderer = TerminalRenderer::new(&mut term, true)?;
979        let mut surf = renderer.surface();
980
981        // draw frame
982        surf.draw_box(Some("fg=#665c54".parse()?));
983        write!(
984            surf.view_mut(0, 2..-1).writer(&ctx),
985            "{}x{}",
986            size.height - 2,
987            size.width - 2,
988        )?;
989
990        // render frame
991        surf.view_mut(1..-1, 1..-1)
992            .iter_mut()
993            .zip(self.surf.iter())
994            .for_each(|(dst, src)| *dst = src.clone());
995        renderer.frame(&mut term)?;
996
997        // move to the end
998        term.execute_many([
999            TerminalCommand::Face(Face::default()),
1000            TerminalCommand::CursorRestore,
1001            TerminalCommand::CursorMove {
1002                row: size.height as i32,
1003                col: 0,
1004            },
1005        ])?;
1006
1007        Ok(term.output)
1008    }
1009
1010    /// Write rendered surface to a file
1011    pub fn save_to_file(&self, path: impl AsRef<std::path::Path>) -> Result<(), Error> {
1012        let file = BufWriter::new(std::fs::File::create(path)?);
1013        self.save(file)?;
1014        Ok(())
1015    }
1016}
1017
1018impl Debug for TerminalSurfaceDebug<'_> {
1019    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1020        let mut cur = std::io::Cursor::new(self.save(Vec::new()).map_err(|_| std::fmt::Error)?);
1021        let mut decoder = crate::decoder::Utf8Decoder::new();
1022        writeln!(fmt)?;
1023        while let Some(chr) = decoder.decode(&mut cur).map_err(|_| std::fmt::Error)? {
1024            write!(fmt, "{}", chr)?;
1025        }
1026        Ok(())
1027    }
1028}
1029
1030struct TerminalDebug<W> {
1031    size: TerminalSize,
1032    encoder: TTYEncoder,
1033    image_handler: KittyImageHandler,
1034    capabilities: TerminalCaps,
1035    output: W,
1036    face: Face,
1037}
1038
1039impl<W: Write + Send> Terminal for TerminalDebug<W> {
1040    fn execute(&mut self, cmd: TerminalCommand) -> Result<(), Error> {
1041        use TerminalCommand::*;
1042        match cmd {
1043            Image(img, pos) => self.image_handler.draw(&mut self.output, &img, pos)?,
1044            ImageErase(img, pos) => self.image_handler.erase(&mut self.output, &img, pos)?,
1045            Face(face) => {
1046                self.face = face;
1047                self.encoder.encode(&mut self.output, cmd)?;
1048            }
1049            CursorTo(pos) => {
1050                // convert absolute position move to relative moves
1051                self.encoder.encode(&mut self.output, CursorRestore)?;
1052                self.encoder.encode(&mut self.output, Face(self.face))?;
1053                self.encoder.encode(
1054                    &mut self.output,
1055                    CursorMove {
1056                        row: pos.row as i32,
1057                        col: pos.col as i32,
1058                    },
1059                )?
1060            }
1061            cmd => self.encoder.encode(&mut self.output, cmd)?,
1062        }
1063        Ok(())
1064    }
1065
1066    fn poll(
1067        &mut self,
1068        _timeout: Option<std::time::Duration>,
1069    ) -> Result<Option<TerminalEvent>, Error> {
1070        Ok(None)
1071    }
1072
1073    fn size(&self) -> Result<TerminalSize, Error> {
1074        Ok(self.size)
1075    }
1076
1077    fn position(&mut self) -> Result<Position, Error> {
1078        Ok(Position::new(0, 0))
1079    }
1080
1081    fn waker(&self) -> TerminalWaker {
1082        TerminalWaker::new(|| Ok(()))
1083    }
1084
1085    fn frames_pending(&self) -> usize {
1086        0
1087    }
1088
1089    fn frames_drop(&mut self) {}
1090
1091    fn dyn_ref(&mut self) -> &mut dyn Terminal {
1092        self
1093    }
1094
1095    fn capabilities(&self) -> &TerminalCaps {
1096        &self.capabilities
1097    }
1098}
1099
1100impl<W: Write> Write for TerminalDebug<W> {
1101    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1102        self.output.write(buf)
1103    }
1104
1105    fn flush(&mut self) -> std::io::Result<()> {
1106        Ok(())
1107    }
1108}
1109
1110#[cfg(test)]
1111mod tests {
1112    use super::*;
1113    use crate::encoder::ColorDepth::TrueColor;
1114
1115    struct DummyTerminal {
1116        size: TerminalSize,
1117        cmds: Vec<TerminalCommand>,
1118        buffer: Vec<u8>,
1119        capabiliets: TerminalCaps,
1120    }
1121
1122    impl DummyTerminal {
1123        fn new(size: Size) -> Self {
1124            Self {
1125                size: TerminalSize {
1126                    cells: size,
1127                    pixels: Size {
1128                        height: size.height * 20,
1129                        width: size.width * 10,
1130                    },
1131                },
1132                cmds: Default::default(),
1133                buffer: Default::default(),
1134                capabiliets: TerminalCaps::default(),
1135            }
1136        }
1137
1138        fn clear(&mut self) {
1139            self.cmds.clear();
1140            self.buffer.clear();
1141        }
1142    }
1143
1144    impl Write for DummyTerminal {
1145        fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1146            self.buffer.write(buf)
1147        }
1148
1149        fn flush(&mut self) -> std::io::Result<()> {
1150            Ok(())
1151        }
1152    }
1153
1154    impl Terminal for DummyTerminal {
1155        fn execute(&mut self, cmd: TerminalCommand) -> Result<(), Error> {
1156            self.cmds.push(cmd);
1157            Ok(())
1158        }
1159
1160        fn poll(
1161            &mut self,
1162            _timeout: Option<std::time::Duration>,
1163        ) -> Result<Option<TerminalEvent>, Error> {
1164            Ok(None)
1165        }
1166
1167        fn size(&self) -> Result<TerminalSize, Error> {
1168            Ok(self.size)
1169        }
1170
1171        fn position(&mut self) -> Result<Position, Error> {
1172            Ok(Position::new(0, 0))
1173        }
1174
1175        fn waker(&self) -> TerminalWaker {
1176            TerminalWaker::new(|| Ok(()))
1177        }
1178
1179        fn frames_pending(&self) -> usize {
1180            0
1181        }
1182
1183        fn frames_drop(&mut self) {}
1184
1185        fn dyn_ref(&mut self) -> &mut dyn Terminal {
1186            self
1187        }
1188
1189        fn capabilities(&self) -> &TerminalCaps {
1190            &self.capabiliets
1191        }
1192    }
1193
1194    #[test]
1195    fn test_render_commands() -> Result<(), Error> {
1196        use TerminalCommand::*;
1197
1198        let purple = "bg=#d3869b".parse()?;
1199        let red = "bg=#fb4934".parse()?;
1200
1201        let mut term = DummyTerminal::new(Size::new(3, 7));
1202        let mut render = TerminalRenderer::new(&mut term, false)?;
1203        let ctx = ViewContext::new(&term)?;
1204
1205        let mut view = render.surface().view_owned(.., 1..);
1206        let mut writer = view.writer(&ctx).with_face(purple);
1207        writer.set_cursor(Position::new(0, 4));
1208
1209        // write with offset
1210        assert_eq!(writer.cursor(), Position::new(0, 4));
1211        write!(writer, "TEST")?;
1212        assert_eq!(writer.cursor(), Position::new(1, 2));
1213        print!(
1214            "[render] writer with offset: {:?}",
1215            render.surface().debug()
1216        );
1217        render.frame(&mut term)?;
1218        assert_eq!(
1219            term.cmds,
1220            vec![
1221                Face(purple),
1222                CursorTo(Position::new(0, 5)),
1223                Char('T'),
1224                Char('E'),
1225                CursorTo(Position::new(1, 1)),
1226                Char('S'),
1227                Char('T'),
1228            ]
1229        );
1230        term.clear();
1231
1232        // erase
1233        render
1234            .surface()
1235            .view_owned(1..2, 1..-1)
1236            .fill(Cell::new_char(red, ' '));
1237        print!("[render] erase: {:?}", render.surface().debug());
1238        render.frame(&mut term)?;
1239        assert_eq!(
1240            term.cmds,
1241            vec![
1242                Face(Default::default()),
1243                // erase is not used as we only need to remove two spaces
1244                CursorTo(Position::new(0, 5)),
1245                Char(' '),
1246                Char(' '),
1247                // erase is used
1248                Face(red),
1249                CursorTo(Position::new(1, 1)),
1250                EraseChars(5)
1251            ]
1252        );
1253        term.clear();
1254
1255        // ascii image
1256        let mut image_surf = SurfaceOwned::new(Size::new(3, 3));
1257        let purple_color = "#d3869b".parse()?;
1258        let green_color = "#b8bb26".parse()?;
1259        image_surf.fill_with(|pos, _| {
1260            if (pos.row + pos.col) % 2 == 0 {
1261                purple_color
1262            } else {
1263                green_color
1264            }
1265        });
1266        let image_ascii = crate::Image::from(image_surf).ascii_view();
1267        render
1268            .surface()
1269            .view_mut(1.., 2..)
1270            .draw_view(&ctx, None, image_ascii)?;
1271        print!("[render] ascii image: {:?}", render.surface().debug());
1272        render.frame(&mut term)?;
1273        assert_eq!(
1274            term.cmds,
1275            vec![
1276                Face(Default::default()),
1277                CursorTo(Position { row: 1, col: 1 }),
1278                Char(' '),
1279                Face("fg=#d3869b, bg=#b8bb26".parse()?),
1280                Char('▀'),
1281                Face("fg=#b8bb26, bg=#d3869b".parse()?),
1282                Char('▀'),
1283                Face("fg=#d3869b, bg=#b8bb26".parse()?),
1284                Char('▀'),
1285                Face(Default::default()),
1286                Char(' '),
1287                Char(' '),
1288                Face("fg=#d3869b".parse()?),
1289                CursorTo(Position { row: 2, col: 2 }),
1290                Char('▀'),
1291                Face("fg=#b8bb26".parse()?),
1292                Char('▀'),
1293                Face("fg=#d3869b".parse()?),
1294                Char('▀')
1295            ]
1296        );
1297        term.clear();
1298
1299        // new line
1300        let mut render = TerminalRenderer::new(&mut term, false)?;
1301        let mut view = render.surface().view_owned(.., 1..-1);
1302        let mut writer = view.writer(&ctx).with_face(purple);
1303        assert_eq!(writer.cursor(), Position::new(0, 0));
1304        write!(&mut writer, "one\ntwo")?;
1305        assert_eq!(writer.cursor(), Position::new(1, 3));
1306        print!("[render] writer new line: {:?}", render.surface().debug());
1307        render.frame(&mut term)?;
1308        assert_eq!(
1309            term.cmds,
1310            vec![
1311                Face(purple),
1312                CursorTo(Position::new(0, 1)),
1313                Char('o'),
1314                Char('n'),
1315                Char('e'),
1316                Char(' '),
1317                Char(' '),
1318                CursorTo(Position::new(1, 1)),
1319                Char('t'),
1320                Char('w'),
1321                Char('o')
1322            ]
1323        );
1324        term.clear();
1325
1326        // new line at the end of line
1327        let mut render = TerminalRenderer::new(&mut term, false)?;
1328        let mut view = render.surface().view_owned(.., 1..-1);
1329        let mut writer = view.writer(&ctx).with_face(purple);
1330        write!(&mut writer, "  one\ntwo")?;
1331        print!(
1332            "[render] writer new line at the end of line: {:?}",
1333            render.surface().debug()
1334        );
1335        render.frame(&mut term)?;
1336        assert_eq!(
1337            term.cmds,
1338            vec![
1339                Face(purple),
1340                CursorTo(Position::new(0, 1)),
1341                Char(' '),
1342                Char(' '),
1343                Char('o'),
1344                Char('n'),
1345                Char('e'),
1346                CursorTo(Position::new(1, 1)),
1347                Char('t'),
1348                Char('w'),
1349                Char('o'),
1350            ]
1351        );
1352        term.clear();
1353
1354        // double with characters
1355        let gray = "bg=#504945".parse()?;
1356        let mut render = TerminalRenderer::new(&mut term, false)?;
1357        let mut view = render.surface().view_owned(.., 1..);
1358        let mut writer = view.writer(&ctx).with_face(gray);
1359        write!(&mut writer, "🤩 awesome 😻|")?;
1360        print!(
1361            "[render] double with characters: {:?}",
1362            render.surface().debug()
1363        );
1364        render.frame(&mut term)?;
1365        assert_eq!(
1366            term.cmds,
1367            vec![
1368                Face(gray),
1369                CursorTo(Position::new(0, 1)),
1370                Char('🤩'),
1371                Char(' '),
1372                Char('a'),
1373                Char('w'),
1374                Char('e'),
1375                CursorTo(Position::new(1, 1)),
1376                Char('s'),
1377                Char('o'),
1378                Char('m'),
1379                Char('e'),
1380                Char(' '),
1381                CursorTo(Position::new(2, 1)),
1382                Char('😻'),
1383                Char('|')
1384            ]
1385        );
1386        term.clear();
1387
1388        Ok(())
1389    }
1390
1391    #[test]
1392    fn test_render_image() -> Result<(), Box<dyn std::error::Error>> {
1393        use TerminalCommand::*;
1394
1395        const ICON: &str = r#"
1396        {
1397            "view_box": [0, 0, 24, 24],
1398            "size": [2, 5],
1399            "path": "M10,17L5,12L6.41,10.58L10,14.17L17.59,6.58L19,8M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z"
1400        }
1401        "#;
1402        let face = "bg=#665c54,fg=#b8bb26".parse()?;
1403        let icon = Cell::new_glyph(face, serde_json::from_str(ICON)?);
1404
1405        let mut term = DummyTerminal::new(Size::new(5, 10));
1406        let mut render = TerminalRenderer::new(&mut term, false)?;
1407        term.clear();
1408
1409        let mut view = render.surface();
1410        view.set(Position::new(1, 2), icon.clone());
1411        print!("[render] image: {:?}", render.surface().debug());
1412        render.frame(&mut term)?;
1413        let img = render
1414            .glyph_cache
1415            .get(&icon)
1416            .expect("image is not rendered")
1417            .clone();
1418        assert_eq!(
1419            term.cmds,
1420            vec![
1421                Face(face),
1422                CursorTo(Position::new(1, 2)),
1423                EraseChars(5),
1424                CursorTo(Position::new(2, 2)),
1425                EraseChars(5),
1426                CursorTo(Position::new(1, 2)),
1427                Image(img.clone(), Position::new(1, 2))
1428            ]
1429        );
1430        term.clear();
1431
1432        let mut view = render.surface();
1433        view.set(Position::new(2, 3), icon.clone());
1434        print!("[render] image move: {:?}", render.surface().debug());
1435        render.frame(&mut term)?;
1436        assert_eq!(
1437            term.cmds,
1438            vec![
1439                // erase old image
1440                ImageErase(img.clone(), Some(Position::new(1, 2))),
1441                Face(crate::Face::default()),
1442                CursorTo(Position::new(1, 2)),
1443                EraseChars(8),
1444                CursorTo(Position::new(2, 2)),
1445                Char(' '),
1446                // draw new image
1447                Face(face),
1448                CursorTo(Position::new(2, 3)),
1449                EraseChars(5),
1450                CursorTo(Position::new(3, 3)),
1451                EraseChars(5),
1452                CursorTo(Position::new(2, 3)),
1453                Image(img, Position::new(2, 3)),
1454            ]
1455        );
1456        term.clear();
1457
1458        Ok(())
1459    }
1460
1461    #[test]
1462    fn test_cell_layout_wrap() {
1463        let max_width = 10;
1464        let ctx = &ViewContext {
1465            pixels_per_cell: Size::new(10, 10),
1466            has_glyphs: true,
1467            color_depth: TrueColor,
1468        };
1469        let face = Face::default();
1470
1471        let mut size = Size::default();
1472        let mut cursor = Position::default();
1473
1474        // empty new line at the start
1475        let pos = Cell::new_char(face, '\n').layout(ctx, max_width, true, &mut size, &mut cursor);
1476        assert!(pos.is_none());
1477        assert_eq!(cursor, Position::new(1, 0));
1478        assert_eq!(size, Size::new(1, 0));
1479
1480        // simple text line
1481        for c in "test".chars() {
1482            Cell::new_char(face, c).layout(ctx, max_width, true, &mut size, &mut cursor);
1483        }
1484        assert_eq!(cursor, Position::new(1, 4));
1485        assert_eq!(size, Size::new(2, 4));
1486
1487        // new line
1488        let pos = Cell::new_char(face, '\n').layout(ctx, max_width, true, &mut size, &mut cursor);
1489        assert!(pos.is_none());
1490        assert_eq!(cursor, Position::new(2, 0));
1491        assert_eq!(size, Size::new(2, 4));
1492
1493        // single width character
1494        let pos = Cell::new_char(face, ' ').layout(ctx, max_width, true, &mut size, &mut cursor);
1495        assert_eq!(pos, Some(Position::new(2, 0)));
1496        assert_eq!(cursor, Position::new(2, 1));
1497        assert_eq!(size, Size::new(3, 4));
1498
1499        // double width character
1500        let pos = Cell::new_char(face, '🤩').layout(ctx, max_width, true, &mut size, &mut cursor);
1501        assert_eq!(pos, Some(Position::new(2, 1)));
1502        assert_eq!(cursor, Position::new(2, 3));
1503        assert_eq!(size, Size::new(3, 4));
1504
1505        // tabulation
1506        let pos = Cell::new_char(face, '\t').layout(ctx, max_width, true, &mut size, &mut cursor);
1507        assert!(pos.is_none());
1508        assert_eq!(cursor, Position::new(2, 8));
1509        assert_eq!(size, Size::new(3, 8));
1510
1511        // zero-width character
1512        let pos = Cell::new_char(face, '\0').layout(ctx, max_width, true, &mut size, &mut cursor);
1513        assert!(pos.is_none());
1514        assert_eq!(cursor, Position::new(2, 8));
1515        assert_eq!(size, Size::new(3, 8));
1516
1517        // single width character close to the end of line
1518        let pos = Cell::new_char(face, 'P').layout(ctx, max_width, true, &mut size, &mut cursor);
1519        assert_eq!(pos, Some(Position::new(2, 8)));
1520        assert_eq!(cursor, Position::new(2, 9));
1521        assert_eq!(size, Size::new(3, 9));
1522
1523        // double width character wraps
1524        let pos = Cell::new_char(face, '🥳').layout(ctx, max_width, true, &mut size, &mut cursor);
1525        assert_eq!(pos, Some(Position::new(3, 0)));
1526        assert_eq!(cursor, Position::new(3, 2));
1527        assert_eq!(size, Size::new(4, 9));
1528
1529        // glyph
1530        let glyph = Glyph::new(
1531            rasterize::Path::empty(),
1532            Default::default(),
1533            None,
1534            Size::new(1, 3),
1535            " ".to_owned(),
1536            None,
1537        );
1538        let pos = Cell::new_glyph(face, glyph).layout(ctx, max_width, true, &mut size, &mut cursor);
1539        assert_eq!(pos, Some(Position::new(3, 2)));
1540        assert_eq!(cursor, Position::new(3, 5));
1541        assert_eq!(size, Size::new(4, 9));
1542
1543        // image
1544        let image = Image::from(SurfaceOwned::new(Size::new(20, 30)));
1545        let image_cell = Cell::new_image(image);
1546        assert_eq!(image_cell.size(ctx), Size::new(2, 3));
1547        let pos = image_cell.layout(ctx, max_width, true, &mut size, &mut cursor);
1548        assert_eq!(pos, Some(Position::new(3, 5)));
1549        assert_eq!(cursor, Position::new(3, 8));
1550        assert_eq!(size, Size::new(5, 9));
1551
1552        // image wrap
1553        let pos = image_cell.layout(ctx, max_width, true, &mut size, &mut cursor);
1554        assert_eq!(pos, Some(Position::new(4, 0)));
1555        assert_eq!(cursor, Position::new(4, 3));
1556        assert_eq!(size, Size::new(6, 9));
1557    }
1558
1559    #[test]
1560    fn test_cell_layout_nowrap() {
1561        let max_width = 5;
1562        let ctx = &ViewContext {
1563            pixels_per_cell: Size::new(10, 10),
1564            has_glyphs: true,
1565            color_depth: TrueColor,
1566        };
1567        let face = Face::default();
1568
1569        let mut size = Size::default();
1570        let mut cursor = Position::default();
1571
1572        // no wrap filler text
1573        for (index, c) in "test".chars().enumerate() {
1574            let pos = Cell::new_char(face, c).layout(ctx, max_width, false, &mut size, &mut cursor);
1575            assert_eq!(pos, Some(Position::new(0, index)));
1576        }
1577        assert_eq!(cursor, Position::new(0, 4));
1578        assert_eq!(size, Size::new(1, 4));
1579
1580        // last cell
1581        let pos = Cell::new_char(face, '_').layout(ctx, max_width, false, &mut size, &mut cursor);
1582        assert_eq!(pos, Some(Position::new(0, 4)));
1583        assert_eq!(cursor, Position::new(0, 5));
1584        assert_eq!(size, Size::new(1, 5));
1585
1586        // no more space
1587        let pos = Cell::new_char(face, '_').layout(ctx, max_width, false, &mut size, &mut cursor);
1588        assert_eq!(pos, None);
1589        assert_eq!(cursor, Position::new(0, 5));
1590        assert_eq!(size, Size::new(1, 5));
1591
1592        // new line
1593        let pos = Cell::new_char(face, '\n').layout(ctx, max_width, false, &mut size, &mut cursor);
1594        assert_eq!(pos, None);
1595        assert_eq!(cursor, Position::new(1, 0));
1596        assert_eq!(size, Size::new(1, 5));
1597
1598        // first cell second line
1599        for (index, c) in "****".chars().enumerate() {
1600            let pos = Cell::new_char(face, c).layout(ctx, max_width, false, &mut size, &mut cursor);
1601            assert_eq!(pos, Some(Position::new(1, index)));
1602            assert_eq!(cursor, Position::new(1, 1 + index));
1603            assert_eq!(size, Size::new(2, 5));
1604        }
1605
1606        // carriage return
1607        let pos = Cell::new_char(face, '\r').layout(ctx, max_width, false, &mut size, &mut cursor);
1608        assert_eq!(pos, None);
1609        assert_eq!(cursor, Position::new(1, 0));
1610        assert_eq!(size, Size::new(2, 5));
1611    }
1612
1613    #[test]
1614    fn test_writer_tab() -> Result<(), Error> {
1615        let guide_face = "bg=#cc241d,fg=#fbf1c7".parse()?;
1616        let underline_face = "bg=#b8bb26,fg=#fbf1c7".parse()?;
1617        let tab_face = "bg=#458588,fg=#fbf1c7".parse()?;
1618        let mark_face = "bg=#b16286,fg=#fbf1c7".parse()?;
1619
1620        let mut term = DummyTerminal::new(Size::new(30, 21));
1621        let mut render = TerminalRenderer::new(&mut term, false)?;
1622        let ctx = ViewContext::new(&term)?;
1623
1624        {
1625            let mut view = render.surface().view_owned(1.., 0).transpose();
1626            let mut writer = view.writer(&ctx).with_face(guide_face);
1627            write!(writer, "012345678901234567890123456789")?;
1628        }
1629
1630        let mut view = render.surface().view_owned(.., 1..);
1631        let mut writer = view.writer(&ctx).with_face(guide_face);
1632        writeln!(writer, "01234567890123456789")?;
1633        writer.set_face(Face::default());
1634
1635        let mut cols = Vec::new();
1636        for index in 0..22 {
1637            writer.set_face(underline_face);
1638            (0..index).for_each(|_| {
1639                writer.put_char('_');
1640            });
1641            writer.set_face(tab_face);
1642            writer.put_char('\t');
1643            writer.set_face(mark_face);
1644            writer.put_char('X');
1645            cols.push(writer.cursor().col);
1646            writer.set_face(Face::default());
1647            writer.put_char('\n');
1648        }
1649        print!("[render] tab writer: {:?}", render.surface().debug());
1650        for (line, col) in cols.into_iter().enumerate() {
1651            assert_eq!((col - 1) % 8, 0, "column {} % 8 != 0 at line {}", col, line);
1652        }
1653        Ok(())
1654    }
1655
1656    #[derive(Default)]
1657    struct DummyCellWrite {
1658        face: Face,
1659        wraps: bool,
1660        cells: Vec<Cell>,
1661    }
1662
1663    impl DummyCellWrite {
1664        fn take(&mut self) -> Vec<Cell> {
1665            std::mem::take(&mut self.cells)
1666        }
1667    }
1668
1669    impl CellWrite for DummyCellWrite {
1670        fn face(&self) -> Face {
1671            self.face
1672        }
1673
1674        fn set_face(&mut self, face: Face) -> Face {
1675            std::mem::replace(&mut self.face, face)
1676        }
1677
1678        fn wraps(&self) -> bool {
1679            self.wraps
1680        }
1681
1682        fn set_wraps(&mut self, wraps: bool) -> bool {
1683            std::mem::replace(&mut self.wraps, wraps)
1684        }
1685
1686        fn put_cell(&mut self, cell: Cell) -> bool {
1687            self.cells.push(cell);
1688            true
1689        }
1690    }
1691
1692    #[test]
1693    fn test_cell_tty_writer() -> Result<(), Error> {
1694        let mut target = DummyCellWrite::default();
1695
1696        write!(target.by_ref().tty_writer(), "\x1b[91mA\x1b[mB")?;
1697        assert_eq!(
1698            target.take(),
1699            vec![
1700                Cell::new_char("fg=#ff0000".parse()?, 'A'),
1701                Cell::new_char(Face::default(), 'B'),
1702            ]
1703        );
1704
1705        Ok(())
1706    }
1707}