ssd1306/mode/
terminal.rs

1#[cfg(feature = "async")]
2use crate::mode::DisplayConfigAsync;
3use crate::{command::AddrMode, mode::DisplayConfig, rotation::DisplayRotation, size::*, Ssd1306};
4#[cfg(feature = "async")]
5use crate::{size::DisplaySizeAsync, Ssd1306Async};
6use core::{cmp::min, fmt};
7#[cfg(feature = "async")]
8use display_interface::AsyncWriteOnlyDataCommand;
9use display_interface::{DisplayError, WriteOnlyDataCommand};
10
11#[maybe_async_cfg::maybe(
12    sync(keep_self),
13    async(
14        feature = "async",
15        idents(
16            TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),
17            DisplaySize(async = "DisplaySizeAsync"),
18        )
19    )
20)]
21/// Extends the [`DisplaySize`](crate::size::DisplaySize) trait
22/// to include number of characters that can fit on the display.
23pub trait TerminalDisplaySize: DisplaySize {
24    /// The number of characters that can fit on the display at once (w * h / (8 * 8))
25    const CHAR_NUM: u8;
26}
27
28#[maybe_async_cfg::maybe(
29    sync(keep_self),
30    async(
31        feature = "async",
32        keep_self,
33        idents(TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),)
34    )
35)]
36impl TerminalDisplaySize for DisplaySize128x64 {
37    const CHAR_NUM: u8 = 128;
38}
39
40#[maybe_async_cfg::maybe(
41    sync(keep_self),
42    async(
43        feature = "async",
44        keep_self,
45        idents(TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),)
46    )
47)]
48impl TerminalDisplaySize for DisplaySize128x32 {
49    const CHAR_NUM: u8 = 64;
50}
51
52#[maybe_async_cfg::maybe(
53    sync(keep_self),
54    async(
55        feature = "async",
56        keep_self,
57        idents(TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),)
58    )
59)]
60impl TerminalDisplaySize for DisplaySize96x16 {
61    const CHAR_NUM: u8 = 24;
62}
63
64#[maybe_async_cfg::maybe(
65    sync(keep_self),
66    async(
67        keep_self,
68        feature = "async",
69        idents(TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),)
70    )
71)]
72impl TerminalDisplaySize for DisplaySize72x40 {
73    const CHAR_NUM: u8 = 45;
74}
75
76#[maybe_async_cfg::maybe(
77    sync(keep_self),
78    async(
79        feature = "async",
80        keep_self,
81        idents(TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),)
82    )
83)]
84impl TerminalDisplaySize for DisplaySize64x48 {
85    const CHAR_NUM: u8 = 48;
86}
87
88/// Contains the new row that the cursor has wrapped around to
89struct CursorWrapEvent(u8);
90
91#[derive(Copy, Clone, Debug)]
92struct Cursor {
93    col: u8,
94    row: u8,
95    width: u8,
96    height: u8,
97}
98
99impl Cursor {
100    pub fn new(width_pixels: u8, height_pixels: u8) -> Self {
101        let width = width_pixels / 8;
102        let height = height_pixels / 8;
103        Cursor {
104            col: 0,
105            row: 0,
106            width,
107            height,
108        }
109    }
110
111    /// Advances the logical cursor by one character.
112    /// Returns a value indicating if this caused the cursor to wrap to the next line or the next
113    /// screen.
114    pub fn advance(&mut self) -> Option<CursorWrapEvent> {
115        self.col = (self.col + 1) % self.width;
116        if self.col == 0 {
117            self.row = (self.row + 1) % self.height;
118            Some(CursorWrapEvent(self.row))
119        } else {
120            None
121        }
122    }
123
124    /// Advances the logical cursor to the start of the next line
125    /// Returns a value indicating the now active line
126    pub fn advance_line(&mut self) -> CursorWrapEvent {
127        self.row = (self.row + 1) % self.height;
128        self.col = 0;
129        CursorWrapEvent(self.row)
130    }
131
132    /// Sets the position of the logical cursor arbitrarily.
133    /// The position will be capped at the maximal possible position.
134    pub fn set_position(&mut self, col: u8, row: u8) {
135        self.col = min(col, self.width - 1);
136        self.row = min(row, self.height - 1);
137    }
138
139    /// Gets the position of the logical cursor on screen in (col, row) order
140    pub fn get_position(&self) -> (u8, u8) {
141        (self.col, self.row)
142    }
143
144    /// Gets the logical dimensions of the screen in terms of characters, as (width, height)
145    pub fn get_dimensions(&self) -> (u8, u8) {
146        (self.width, self.height)
147    }
148}
149
150/// Errors which can occur when interacting with the terminal mode
151#[derive(Clone)]
152pub enum TerminalModeError {
153    /// An error occurred in the underlying interface layer
154    InterfaceError(DisplayError),
155    /// The mode was used before it was initialized
156    Uninitialized,
157    /// A location was specified outside the bounds of the screen
158    OutOfBounds,
159}
160
161impl fmt::Debug for TerminalModeError {
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
163        match self {
164            Self::InterfaceError(_) => "InterfaceError".fmt(f),
165            Self::Uninitialized => "Uninitialized".fmt(f),
166            Self::OutOfBounds => "OutOfBound".fmt(f),
167        }
168    }
169}
170
171impl From<DisplayError> for TerminalModeError {
172    fn from(value: DisplayError) -> Self {
173        TerminalModeError::InterfaceError(value)
174    }
175}
176
177/// Terminal mode.
178#[maybe_async_cfg::maybe(
179    sync(keep_self),
180    async(feature = "async", idents(TerminalMode(async = "TerminalModeAsync")))
181)]
182#[derive(Debug, Copy, Clone, Default)]
183pub struct TerminalMode {
184    cursor: Option<Cursor>,
185}
186
187#[maybe_async_cfg::maybe(
188    sync(keep_self),
189    async(feature = "async", idents(TerminalMode(async = "TerminalModeAsync")))
190)]
191impl TerminalMode {
192    /// Create a new terminal mode config instance.
193    pub fn new() -> Self {
194        Self::default()
195    }
196}
197
198#[maybe_async_cfg::maybe(
199    sync(keep_self),
200    async(
201        feature = "async",
202        idents(
203            DisplaySize(async = "DisplaySizeAsync"),
204            DisplayConfig(async = "DisplayConfigAsync"),
205            WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand"),
206            TerminalMode(async = "TerminalModeAsync"),
207            TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),
208        )
209    )
210)]
211impl<DI, SIZE> DisplayConfig for Ssd1306<DI, SIZE, TerminalMode>
212where
213    DI: WriteOnlyDataCommand,
214    SIZE: TerminalDisplaySize,
215{
216    type Error = TerminalModeError;
217
218    /// Set the display rotation
219    ///
220    /// This method resets the cursor but does not clear the screen.
221    async fn set_rotation(&mut self, rot: DisplayRotation) -> Result<(), TerminalModeError> {
222        self.set_rotation(rot).await?;
223        // Need to reset cursor position, otherwise coordinates can become invalid
224        self.reset_pos().await
225    }
226
227    /// Initialise the display in page mode (i.e. a byte walks down a column of 8 pixels) with
228    /// column 0 on the left and column _(SIZE::Width::U8 - 1)_ on the right, but no automatic line
229    /// wrapping.
230    async fn init(&mut self) -> Result<(), TerminalModeError> {
231        self.init_with_addr_mode(AddrMode::Page).await?;
232        self.reset_pos().await
233    }
234}
235
236#[maybe_async_cfg::maybe(
237    sync(keep_self),
238    async(
239        feature = "async",
240        idents(
241            DisplaySize(async = "DisplaySizeAsync"),
242            DisplayConfig(async = "DisplayConfigAsync"),
243            WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand"),
244            TerminalMode(async = "TerminalModeAsync"),
245            TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),
246        )
247    )
248)]
249impl<DI, SIZE> Ssd1306<DI, SIZE, TerminalMode>
250where
251    DI: WriteOnlyDataCommand,
252    SIZE: TerminalDisplaySize,
253{
254    /// Clear the display and reset the cursor to the top left corner
255    pub async fn clear(&mut self) -> Result<(), TerminalModeError> {
256        // Let the chip handle line wrapping so we can fill the screen with blanks faster
257        self.set_addr_mode(AddrMode::Horizontal).await?;
258
259        let offset_x = match self.rotation() {
260            DisplayRotation::Rotate0 | DisplayRotation::Rotate270 => SIZE::OFFSETX,
261            DisplayRotation::Rotate180 | DisplayRotation::Rotate90 => {
262                // If segment remapping is flipped, we need to calculate
263                // the offset from the other edge of the display.
264                SIZE::DRIVER_COLS - SIZE::WIDTH - SIZE::OFFSETX
265            }
266        };
267        self.set_draw_area(
268            (offset_x, SIZE::OFFSETY),
269            (SIZE::WIDTH + offset_x, SIZE::HEIGHT + SIZE::OFFSETY),
270        )
271        .await?;
272
273        // Clear the display
274        for _ in 0..SIZE::CHAR_NUM {
275            self.draw(&[0; 8]).await?;
276        }
277
278        // But for normal operation we manage the line wrapping
279        self.set_addr_mode(AddrMode::Page).await?;
280        self.reset_pos().await
281    }
282
283    /// Print a character to the display
284    pub async fn print_char(&mut self, c: char) -> Result<(), TerminalModeError> {
285        match c {
286            '\n' => {
287                let CursorWrapEvent(new_line) = self.ensure_cursor()?.advance_line();
288                self.set_position(0, new_line).await?;
289            }
290            '\r' => {
291                self.set_column(0).await?;
292                let (_, cur_line) = self.ensure_cursor()?.get_position();
293                self.ensure_cursor()?.set_position(0, cur_line);
294            }
295            _ => {
296                let bitmap = match self.rotation {
297                    DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => {
298                        Self::char_to_bitmap(c)
299                    }
300                    DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => {
301                        let bitmap = Self::char_to_bitmap(c);
302                        Self::rotate_bitmap(bitmap)
303                    }
304                };
305
306                self.draw(&bitmap).await?;
307
308                // Increment character counter and potentially wrap line
309                self.advance_cursor().await?;
310            }
311        }
312
313        Ok(())
314    }
315
316    /// Get the current cursor position, in character coordinates.
317    /// This is the (column, row) that the next character will be written to.
318    pub fn position(&self) -> Result<(u8, u8), TerminalModeError> {
319        self.mode
320            .cursor
321            .as_ref()
322            .map(Cursor::get_position)
323            .ok_or(TerminalModeError::Uninitialized)
324    }
325
326    /// Set the cursor position, in character coordinates.
327    /// This is the (column, row) that the next character will be written to.
328    /// If the position is out of bounds, an Err will be returned.
329    pub async fn set_position(&mut self, column: u8, row: u8) -> Result<(), TerminalModeError> {
330        let (width, height) = self.ensure_cursor()?.get_dimensions();
331        if column >= width || row >= height {
332            Err(TerminalModeError::OutOfBounds)
333        } else {
334            let offset_x = match self.rotation() {
335                DisplayRotation::Rotate0 | DisplayRotation::Rotate270 => SIZE::OFFSETX,
336                DisplayRotation::Rotate180 | DisplayRotation::Rotate90 => {
337                    // If segment remapping is flipped, we need to calculate
338                    // the offset from the other edge of the display.
339                    SIZE::DRIVER_COLS - SIZE::WIDTH - SIZE::OFFSETX
340                }
341            };
342            match self.rotation() {
343                DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => {
344                    self.set_column(offset_x + column * 8).await?;
345                    self.set_row(SIZE::OFFSETY + row * 8).await?;
346                }
347                DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => {
348                    self.set_column(offset_x + row * 8).await?;
349                    self.set_row(SIZE::OFFSETY + column * 8).await?;
350                }
351            }
352            self.ensure_cursor()?.set_position(column, row);
353            Ok(())
354        }
355    }
356
357    /// Reset the draw area and move pointer to the top left corner
358    async fn reset_pos(&mut self) -> Result<(), TerminalModeError> {
359        // Initialise the counter when we know it's valid
360        let (w, h) = match self.rotation() {
361            DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => (SIZE::WIDTH, SIZE::HEIGHT),
362            DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => (SIZE::HEIGHT, SIZE::WIDTH),
363        };
364        self.mode.cursor = Some(Cursor::new(w, h));
365
366        // Reset cursor position
367        self.set_position(0, 0).await
368    }
369
370    /// Advance the cursor, automatically wrapping lines and/or screens if necessary
371    /// Takes in an already-unwrapped cursor to avoid re-unwrapping
372    async fn advance_cursor(&mut self) -> Result<(), TerminalModeError> {
373        let cursor = self.ensure_cursor()?;
374
375        cursor.advance();
376        let (c, r) = cursor.get_position();
377        self.set_position(c, r).await
378    }
379
380    fn ensure_cursor(&mut self) -> Result<&mut Cursor, TerminalModeError> {
381        self.mode
382            .cursor
383            .as_mut()
384            .ok_or(TerminalModeError::Uninitialized)
385    }
386
387    fn char_to_bitmap(input: char) -> [u8; 8] {
388        const CHARS: [[u8; 6]; 95] = [
389            // !
390            [0x00, 0x2f, 0x00, 0x00, 0x00, 0x00],
391            // "
392            [0x03, 0x00, 0x03, 0x00, 0x00, 0x00],
393            // #
394            [0x12, 0x3f, 0x12, 0x12, 0x3f, 0x12],
395            // $
396            [0x2e, 0x2a, 0x7f, 0x2a, 0x3a, 0x00],
397            // %
398            [0x23, 0x13, 0x08, 0x04, 0x32, 0x31],
399            // &
400            [0x10, 0x2a, 0x25, 0x2a, 0x10, 0x20],
401            // '
402            [0x02, 0x01, 0x00, 0x00, 0x00, 0x00],
403            // (
404            [0x1e, 0x21, 0x00, 0x00, 0x00, 0x00],
405            // )
406            [0x21, 0x1e, 0x00, 0x00, 0x00, 0x00],
407            // *
408            [0x08, 0x2a, 0x1c, 0x2a, 0x08, 0x00],
409            // +
410            [0x08, 0x08, 0x3e, 0x08, 0x08, 0x00],
411            // ,
412            [0x80, 0x60, 0x00, 0x00, 0x00, 0x00],
413            // -
414            [0x08, 0x08, 0x08, 0x08, 0x08, 0x00],
415            // .
416            [0x30, 0x30, 0x00, 0x00, 0x00, 0x00],
417            // /
418            [0x20, 0x10, 0x08, 0x04, 0x02, 0x00],
419            // 0
420            [0x1e, 0x31, 0x29, 0x25, 0x23, 0x1e],
421            // 1
422            [0x22, 0x21, 0x3f, 0x20, 0x20, 0x20],
423            // 2
424            [0x32, 0x29, 0x29, 0x29, 0x29, 0x26],
425            // 3
426            [0x12, 0x21, 0x21, 0x25, 0x25, 0x1a],
427            // 4
428            [0x18, 0x14, 0x12, 0x3f, 0x10, 0x00],
429            // 5
430            [0x17, 0x25, 0x25, 0x25, 0x25, 0x19],
431            // 6
432            [0x1e, 0x25, 0x25, 0x25, 0x25, 0x18],
433            // 7
434            [0x01, 0x01, 0x31, 0x09, 0x05, 0x03],
435            // 8
436            [0x1a, 0x25, 0x25, 0x25, 0x25, 0x1a],
437            // 9
438            [0x06, 0x29, 0x29, 0x29, 0x29, 0x1e],
439            // :
440            [0x24, 0x00, 0x00, 0x00, 0x00, 0x00],
441            // ;
442            [0x80, 0x64, 0x00, 0x00, 0x00, 0x00],
443            // <
444            [0x08, 0x14, 0x22, 0x00, 0x00, 0x00],
445            // =
446            [0x14, 0x14, 0x14, 0x14, 0x14, 0x00],
447            // >
448            [0x22, 0x14, 0x08, 0x00, 0x00, 0x00],
449            // ?
450            [0x02, 0x01, 0x01, 0x29, 0x05, 0x02],
451            // @
452            [0x1e, 0x21, 0x2d, 0x2b, 0x2d, 0x0e],
453            // A
454            [0x3e, 0x09, 0x09, 0x09, 0x09, 0x3e],
455            // B
456            [0x3f, 0x25, 0x25, 0x25, 0x25, 0x1a],
457            // C
458            [0x1e, 0x21, 0x21, 0x21, 0x21, 0x12],
459            // D
460            [0x3f, 0x21, 0x21, 0x21, 0x12, 0x0c],
461            // E
462            [0x3f, 0x25, 0x25, 0x25, 0x25, 0x21],
463            // F
464            [0x3f, 0x05, 0x05, 0x05, 0x05, 0x01],
465            // G
466            [0x1e, 0x21, 0x21, 0x21, 0x29, 0x1a],
467            // H
468            [0x3f, 0x04, 0x04, 0x04, 0x04, 0x3f],
469            // I
470            [0x21, 0x21, 0x3f, 0x21, 0x21, 0x00],
471            // J
472            [0x10, 0x20, 0x20, 0x20, 0x20, 0x1f],
473            // K
474            [0x3f, 0x04, 0x0c, 0x0a, 0x11, 0x20],
475            // L
476            [0x3f, 0x20, 0x20, 0x20, 0x20, 0x20],
477            // M
478            [0x3f, 0x02, 0x04, 0x04, 0x02, 0x3f],
479            // N
480            [0x3f, 0x02, 0x04, 0x08, 0x10, 0x3f],
481            // O
482            [0x1e, 0x21, 0x21, 0x21, 0x21, 0x1e],
483            // P
484            [0x3f, 0x09, 0x09, 0x09, 0x09, 0x06],
485            // Q
486            [0x1e, 0x21, 0x29, 0x31, 0x21, 0x5e],
487            // R
488            [0x3f, 0x09, 0x09, 0x09, 0x19, 0x26],
489            // S
490            [0x12, 0x25, 0x25, 0x25, 0x25, 0x18],
491            // T
492            [0x01, 0x01, 0x01, 0x3f, 0x01, 0x01],
493            // U
494            [0x1f, 0x20, 0x20, 0x20, 0x20, 0x1f],
495            // V
496            [0x0f, 0x10, 0x20, 0x20, 0x10, 0x0f],
497            // W
498            [0x1f, 0x20, 0x10, 0x10, 0x20, 0x1f],
499            // X
500            [0x21, 0x12, 0x0c, 0x0c, 0x12, 0x21],
501            // Y
502            [0x01, 0x02, 0x3c, 0x02, 0x01, 0x00],
503            // Z
504            [0x21, 0x31, 0x29, 0x25, 0x23, 0x21],
505            // [
506            [0x3f, 0x21, 0x00, 0x00, 0x00, 0x00],
507            // \
508            [0x02, 0x04, 0x08, 0x10, 0x20, 0x00],
509            // ]
510            [0x21, 0x3f, 0x00, 0x00, 0x00, 0x00],
511            // ^
512            [0x04, 0x02, 0x3f, 0x02, 0x04, 0x00],
513            // _
514            [0x40, 0x40, 0x40, 0x40, 0x40, 0x40],
515            // `
516            [0x01, 0x02, 0x00, 0x00, 0x00, 0x00],
517            // a
518            [0x10, 0x2a, 0x2a, 0x2a, 0x3c, 0x00],
519            // b
520            [0x3f, 0x24, 0x24, 0x24, 0x18, 0x00],
521            // c
522            [0x1c, 0x22, 0x22, 0x22, 0x00, 0x00],
523            // d
524            [0x18, 0x24, 0x24, 0x24, 0x3f, 0x00],
525            // e
526            [0x1c, 0x2a, 0x2a, 0x2a, 0x24, 0x00],
527            // f
528            [0x00, 0x3e, 0x05, 0x01, 0x00, 0x00],
529            // g
530            [0x18, 0xa4, 0xa4, 0xa4, 0x7c, 0x00],
531            // h
532            [0x3f, 0x04, 0x04, 0x04, 0x38, 0x00],
533            // i
534            [0x00, 0x24, 0x3d, 0x20, 0x00, 0x00],
535            // j
536            [0x20, 0x40, 0x40, 0x3d, 0x00, 0x00],
537            // k
538            [0x3f, 0x0c, 0x12, 0x20, 0x00, 0x00],
539            // l
540            [0x1f, 0x20, 0x20, 0x00, 0x00, 0x00],
541            // m
542            [0x3e, 0x02, 0x3c, 0x02, 0x3c, 0x00],
543            // n
544            [0x3e, 0x02, 0x02, 0x02, 0x3c, 0x00],
545            // o
546            [0x1c, 0x22, 0x22, 0x22, 0x1c, 0x00],
547            // p
548            [0xfc, 0x24, 0x24, 0x24, 0x18, 0x00],
549            // q
550            [0x18, 0x24, 0x24, 0x24, 0xfc, 0x00],
551            // r
552            [0x3e, 0x04, 0x02, 0x02, 0x00, 0x00],
553            // s
554            [0x24, 0x2a, 0x2a, 0x2a, 0x10, 0x00],
555            // t
556            [0x02, 0x1f, 0x22, 0x20, 0x00, 0x00],
557            // u
558            [0x1e, 0x20, 0x20, 0x20, 0x1e, 0x00],
559            // v
560            [0x06, 0x18, 0x20, 0x18, 0x06, 0x00],
561            // w
562            [0x1e, 0x30, 0x1c, 0x30, 0x1e, 0x00],
563            // x
564            [0x22, 0x14, 0x08, 0x14, 0x22, 0x00],
565            // y
566            [0x1c, 0xa0, 0xa0, 0xa0, 0x7c, 0x00],
567            // z
568            [0x22, 0x32, 0x2a, 0x26, 0x22, 0x00],
569            // {
570            [0x0c, 0x3f, 0x21, 0x00, 0x00, 0x00],
571            // |
572            [0x3f, 0x00, 0x00, 0x00, 0x00, 0x00],
573            // }
574            [0x21, 0x3f, 0x0c, 0x00, 0x00, 0x00],
575            // ~
576            [0x02, 0x01, 0x02, 0x01, 0x00, 0x00],
577            // blank
578            [0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
579        ];
580
581        let g = (input as usize)
582            .checked_sub(b'!'.into())
583            .and_then(|idx| CHARS.get(idx))
584            .unwrap_or(&CHARS[CHARS.len() - 1]);
585
586        [0, g[0], g[1], g[2], g[3], g[4], g[5], 0]
587    }
588
589    fn rotate_bitmap(bitmap: [u8; 8]) -> [u8; 8] {
590        let mut rotated: [u8; 8] = [0; 8];
591
592        for (col, source) in bitmap.iter().enumerate() {
593            // source.msb is the top pixel
594            for (row, item) in rotated.iter_mut().enumerate() {
595                let bit = source & (1 << row) != 0;
596                if bit {
597                    *item |= 1 << col;
598                }
599            }
600        }
601
602        rotated
603    }
604}
605
606#[cfg(feature = "async")]
607impl<DI, SIZE> Ssd1306Async<DI, SIZE, TerminalModeAsync>
608where
609    DI: AsyncWriteOnlyDataCommand,
610    SIZE: TerminalDisplaySizeAsync,
611{
612    /// Write a string slice to the display
613    pub async fn write_str(&mut self, s: &str) -> Result<(), TerminalModeError> {
614        for c in s.chars() {
615            self.print_char(c).await?;
616        }
617        Ok(())
618    }
619}
620
621impl<DI, SIZE> fmt::Write for Ssd1306<DI, SIZE, TerminalMode>
622where
623    DI: WriteOnlyDataCommand,
624    SIZE: TerminalDisplaySize,
625{
626    fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
627        s.chars().map(move |c| self.print_char(c)).next_back();
628        Ok(())
629    }
630}