ansi: Designate and invoke graphic character sets

Implement the designation of graphic character sets G0-G3 to ASCII or the
Special character and line drawing glyphs. As well as the invokation/selection
of the character sets (shift in, shift out and lock shifting).
This commit is contained in:
Richard Palethorpe 2017-01-09 21:07:23 +01:00 committed by Joe Wilm
parent 6d0abe2607
commit 6229477190
2 changed files with 236 additions and 8 deletions

View File

@ -238,6 +238,18 @@ pub trait Handler {
/// DECKPNM - Set keypad to numeric mode (digits intead of ESCape seq)
fn unset_keypad_application_mode(&mut self) {}
/// Set one of the graphic character sets, G0 to G3, as the active charset.
///
/// 'Invoke' one of G0 to G3 in the GL area. Also refered to as shift in,
/// shift out and locking shift depending on the set being activated
fn set_active_charset(&mut self, CharsetIndex) {}
/// Assign a graphic character set to G0, G1, G2 or G3
///
/// 'Designate' a graphic character set as one of G0 to G3, so that it can
/// later be 'invoked' by `set_active_charset`
fn configure_charset(&mut self, CharsetIndex, StandardCharset) {}
}
/// Terminal modes
@ -436,6 +448,23 @@ pub enum Attr {
Background(Color),
}
/// Identifiers which can be assigned to a graphic character set
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CharsetIndex {
/// Default set, is designated as ASCII at startup
G0,
G1,
G2,
G3,
}
/// Standard or common character sets which can be designated as G0-G3
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum StandardCharset {
Ascii,
SpecialCharacterAndLineDrawing,
}
impl<'a, H, W> vte::Perform for Performer<'a, H, W>
where H: Handler + TermInfo + 'a,
W: io::Write + 'a
@ -454,6 +483,8 @@ impl<'a, H, W> vte::Perform for Performer<'a, H, W>
C0::LF | C0::VT | C0::FF => self.handler.linefeed(),
C0::BEL => self.handler.bell(),
C0::SUB => self.handler.substitute(),
C0::SI => self.handler.set_active_charset(CharsetIndex::G0),
C0::SO => self.handler.set_active_charset(CharsetIndex::G1),
C1::NEL => self.handler.newline(),
C1::HTS => self.handler.set_horizontal_tabstop(),
C1::DECID => self.handler.identify_terminal(self.writer),
@ -742,19 +773,41 @@ impl<'a, H, W> vte::Perform for Performer<'a, H, W>
_ignore: bool,
byte: u8
) {
macro_rules! unhandled {
() => {{
err_println!("[unhandled] esc_dispatch params={:?}, ints={:?}, byte={:?} ({:02x})",
params, intermediates, byte as char, byte);
return;
}}
}
macro_rules! configure_charset {
($charset:path) => {{
let index: CharsetIndex = match intermediates.first().cloned() {
Some(b'(') => CharsetIndex::G0,
Some(b')') => CharsetIndex::G1,
Some(b'*') => CharsetIndex::G2,
Some(b'+') => CharsetIndex::G3,
_ => unhandled!(),
};
self.handler.configure_charset(index, $charset)
}}
}
match byte {
b'B' => configure_charset!(StandardCharset::Ascii),
b'D' => self.handler.linefeed(),
b'E' => self.handler.newline(),
b'H' => self.handler.set_horizontal_tabstop(),
b'M' => self.handler.reverse_index(),
b'Z' => self.handler.identify_terminal(self.writer),
b'c' => self.handler.reset_state(),
b'0' => configure_charset!(StandardCharset::SpecialCharacterAndLineDrawing),
b'7' => self.handler.save_cursor_position(),
b'8' => self.handler.restore_cursor_position(),
b'=' => self.handler.set_keypad_application_mode(),
b'>' => self.handler.unset_keypad_application_mode(),
_ => err_println!("[unhandled] esc_dispatch params={:?}, ints={:?}, byte={:?} ({:02x})",
params, intermediates, byte as char, byte),
_ => unhandled!(),
}
}
}
@ -969,7 +1022,7 @@ pub mod C1 {
mod tests {
use std::io;
use index::{Line, Column};
use super::{Processor, Handler, Attr, TermInfo, Color};
use super::{Processor, Handler, Attr, TermInfo, Color, StandardCharset, CharsetIndex};
use ::Rgb;
/// The /dev/null of io::Write
@ -1074,4 +1127,67 @@ mod tests {
parser.advance(&mut handler, *byte, &mut Void);
}
}
struct CharsetHandler {
index: CharsetIndex,
charset: StandardCharset,
}
impl Default for CharsetHandler {
fn default() -> CharsetHandler {
CharsetHandler {
index: CharsetIndex::G0,
charset: StandardCharset::Ascii,
}
}
}
impl Handler for CharsetHandler {
fn configure_charset(&mut self, index: CharsetIndex, charset: StandardCharset) {
self.index = index;
self.charset = charset;
}
fn set_active_charset(&mut self, index: CharsetIndex) {
self.index = index;
}
}
impl TermInfo for CharsetHandler {
fn lines(&self) -> Line { Line(200) }
fn cols(&self) -> Column { Column(90) }
}
#[test]
fn parse_designate_g0_as_line_drawing() {
static BYTES: &'static [u8] = &[0x1b, b'(', b'0'];
let mut parser = Processor::new();
let mut handler = CharsetHandler::default();
for byte in &BYTES[..] {
parser.advance(&mut handler, *byte, &mut Void);
}
assert_eq!(handler.index, CharsetIndex::G0);
assert_eq!(handler.charset, StandardCharset::SpecialCharacterAndLineDrawing);
}
#[test]
fn parse_designate_g1_as_line_drawing_and_invoke() {
static BYTES: &'static [u8] = &[0x1b, 0x29, 0x30, 0x0e];
let mut parser = Processor::new();
let mut handler = CharsetHandler::default();
for byte in &BYTES[..3] {
parser.advance(&mut handler, *byte, &mut Void);
}
assert_eq!(handler.index, CharsetIndex::G1);
assert_eq!(handler.charset, StandardCharset::SpecialCharacterAndLineDrawing);
let mut handler = CharsetHandler::default();
parser.advance(&mut handler, BYTES[3], &mut Void);
assert_eq!(handler.index, CharsetIndex::G1);
}
}

View File

@ -13,12 +13,12 @@
// limitations under the License.
//
//! Exports the `Term` type which is a high-level API for the Grid
use std::ops::{Deref, Range};
use std::ops::{Deref, Range, Index, IndexMut};
use std::ptr;
use std::cmp::min;
use std::io;
use ansi::{self, Color, NamedColor, Attr, Handler};
use ansi::{self, Color, NamedColor, Attr, Handler, CharsetIndex, StandardCharset};
use grid::{Grid, ClearRegion, ToRange};
use index::{self, Point, Column, Line, Linear, IndexRange, Contains, RangeInclusive};
use selection::{Span, Selection};
@ -200,6 +200,79 @@ pub use self::mode::TermMode;
pub const TAB_SPACES: usize = 8;
trait CharsetMapping {
fn map(&self, c: char) -> char {
c
}
}
impl CharsetMapping for StandardCharset {
/// Switch/Map character to the active charset. Ascii is the common case and
/// for that we want to do as little as possible.
#[inline]
fn map(&self, c: char) -> char {
match *self {
StandardCharset::Ascii => c,
StandardCharset::SpecialCharacterAndLineDrawing =>
match c {
'`' => '◆',
'a' => '▒',
'b' => '\t',
'c' => '\u{000c}',
'd' => '\r',
'e' => '\n',
'f' => '°',
'g' => '±',
'h' => '\u{2424}',
'i' => '\u{000b}',
'j' => '┘',
'k' => '┐',
'l' => '┌',
'm' => '└',
'n' => '┼',
'o' => '⎺',
'p' => '⎻',
'q' => '─',
'r' => '⎼',
's' => '⎽',
't' => '├',
'u' => '┤',
'v' => '┴',
'w' => '┬',
'x' => '│',
'y' => '≤',
'z' => '≥',
'{' => 'π',
'|' => '≠',
'}' => '£',
'~' => '·',
_ => c
},
}
}
}
struct Charsets([StandardCharset; 4]);
impl Charsets {
fn new() -> Charsets {
Charsets([StandardCharset::Ascii; 4])
}
}
impl Index<CharsetIndex> for Charsets {
type Output = StandardCharset;
fn index(&self, index: CharsetIndex) -> &StandardCharset {
&self.0[index as usize]
}
}
impl IndexMut<CharsetIndex> for Charsets {
fn index_mut(&mut self, index: CharsetIndex) -> &mut StandardCharset {
&mut self.0[index as usize]
}
}
pub struct Term {
/// The grid
grid: Grid<Cell>,
@ -228,6 +301,13 @@ pub struct Term {
/// Alt cursor
alt_cursor: Point,
/// Currently configured graphic character sets
charsets: Charsets,
/// The graphic character set, out of `charsets`, which ASCII is currently
/// being mapped to
active_charset: CharsetIndex,
/// Tabstops
tabs: Vec<bool>,
@ -323,6 +403,8 @@ impl Term {
alt: false,
cursor: Point::default(),
alt_cursor: Point::default(),
active_charset: CharsetIndex::G0,
charsets: Charsets::new(),
tabs: tabs,
mode: Default::default(),
scroll_region: scroll_region,
@ -695,7 +777,7 @@ impl ansi::Handler for Term {
{
let cell = &mut self.grid[&self.cursor];
*cell = self.template_cell;
cell.c = c;
cell.c = self.charsets[self.active_charset].map(c);
}
if (self.cursor.col + 1) < self.grid.num_cols() {
@ -1109,6 +1191,18 @@ impl ansi::Handler for Term {
debug_println!("unset mode::APP_KEYPAD");
self.mode.remove(mode::APP_KEYPAD);
}
#[inline]
fn configure_charset(&mut self, index: CharsetIndex, charset: StandardCharset) {
debug_println!("designate {:?} character set as {:?}", index, charset);
self.charsets[index] = charset;
}
#[inline]
fn set_active_charset(&mut self, index: CharsetIndex) {
debug_println!("Activate {:?} character set", index);
self.active_charset = index;
}
}
#[cfg(test)]
@ -1116,11 +1210,12 @@ mod tests {
extern crate serde_json;
extern crate test;
use super::limit;
use super::{Term, limit, SizeInfo};
use grid::Grid;
use index::{Line, Column};
use index::{Point, Line, Column};
use term::{Cell};
use ansi::{Handler, CharsetIndex, StandardCharset};
/// Check that the grid can be serialized back and forth losslessly
///
@ -1144,6 +1239,23 @@ mod tests {
assert_eq!(limit(5, 6, 10), 6);
assert_eq!(limit(5, 1, 4), 4);
}
#[test]
fn input_line_drawing_character() {
let size = SizeInfo {
width: 21.0,
height: 51.0,
cell_width: 3.0,
cell_height: 3.0,
};
let mut term = Term::new(size);
let cursor = Point::new(Line(0), Column(0));
term.configure_charset(CharsetIndex::G0,
StandardCharset::SpecialCharacterAndLineDrawing);
term.input('a');
assert_eq!(term.grid()[&cursor].c, '▒');
}
}
#[cfg(test)]