mirror of
https://github.com/sharkdp/hexyl.git
synced 2026-10-26 20:16:31 +00:00
Add new braille character table and color schemes (#247)
* Add new braille character table and color schemes * Add character for tab and change color for 0x7f --------- Co-authored-by: David Peter <mail@david-peter.de>
This commit is contained in:
1 parent
9ef7c346dd
commit
f2547aed69
4 files changed
+179
-14
No files matched your search
@@ -4,6 +4,9 @@
|
||||
|
||||
## Features
|
||||
|
||||
- Add `--color-scheme` option, see #247 (@aticu)
|
||||
- Add `braille` character table, see #247 (@aticu)
|
||||
|
||||
## Bugfixes
|
||||
|
||||
|
||||
|
||||
@@ -35,6 +35,81 @@ fn init_color(name: &str, default_ansi: AnsiColors) -> String {
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
pub const COLOR_NULL_RGB: &[u8] = &rgb_bytes(100, 100, 100);
|
||||
|
||||
pub const COLOR_DEL: &[u8] = &rgb_bytes(64, 128, 0);
|
||||
|
||||
pub const COLOR_GRADIENT_NONASCII: [[u8; 19]; 128] =
|
||||
generate_color_gradient(&[(255, 0, 0, 0.0), (255, 255, 0, 0.66), (255, 255, 255, 1.0)]);
|
||||
|
||||
pub const COLOR_GRADIENT_ASCII_NONPRINTABLE: [[u8; 19]; 31] =
|
||||
generate_color_gradient(&[(255, 0, 255, 0.0), (128, 0, 255, 1.0)]);
|
||||
|
||||
pub const COLOR_GRADIENT_ASCII_PRINTABLE: [[u8; 19]; 95] =
|
||||
generate_color_gradient(&[(0, 128, 255, 0.0), (0, 255, 128, 1.0)]);
|
||||
|
||||
const fn as_dec(byte: u8) -> [u8; 3] {
|
||||
[
|
||||
b'0' + (byte / 100),
|
||||
b'0' + ((byte % 100) / 10),
|
||||
b'0' + (byte % 10),
|
||||
]
|
||||
}
|
||||
|
||||
const fn rgb_bytes(r: u8, g: u8, b: u8) -> [u8; 19] {
|
||||
let mut buf = *b"\x1b[38;2;rrr;ggg;bbbm";
|
||||
|
||||
// r 7
|
||||
buf[7] = as_dec(r)[0];
|
||||
buf[8] = as_dec(r)[1];
|
||||
buf[9] = as_dec(r)[2];
|
||||
|
||||
// g 11
|
||||
buf[11] = as_dec(g)[0];
|
||||
buf[12] = as_dec(g)[1];
|
||||
buf[13] = as_dec(g)[2];
|
||||
|
||||
// b 15
|
||||
buf[15] = as_dec(b)[0];
|
||||
buf[16] = as_dec(b)[1];
|
||||
buf[17] = as_dec(b)[2];
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
const fn generate_color_gradient<const N: usize>(stops: &[(u8, u8, u8, f64)]) -> [[u8; 19]; N] {
|
||||
let mut out = [rgb_bytes(0, 0, 0); N];
|
||||
|
||||
assert!(stops.len() >= 2, "need at least two stops for the gradient");
|
||||
|
||||
let mut byte = 0;
|
||||
while byte < N {
|
||||
let relative_byte = byte as f64 / N as f64;
|
||||
|
||||
let mut i = 1;
|
||||
while i < stops.len() && stops[i].3 < relative_byte {
|
||||
i += 1;
|
||||
}
|
||||
if i >= stops.len() {
|
||||
i = stops.len() - 1;
|
||||
}
|
||||
let prev_stop = stops[i - 1];
|
||||
let stop = stops[i];
|
||||
let diff = stop.3 - prev_stop.3;
|
||||
let t = (relative_byte - prev_stop.3) / diff;
|
||||
|
||||
let r = (prev_stop.0 as f64 + (t * (stop.0 as f64 - prev_stop.0 as f64))) as u8;
|
||||
let g = (prev_stop.1 as f64 + (t * (stop.1 as f64 - prev_stop.1 as f64))) as u8;
|
||||
let b = (prev_stop.2 as f64 + (t * (stop.2 as f64 - prev_stop.2 as f64))) as u8;
|
||||
|
||||
out[byte] = rgb_bytes(r, g, b);
|
||||
|
||||
byte += 1;
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub const CP437: [char; 256] = [
|
||||
// Copyright (c) 2016, Delan Azabani <delan@azabani.com>
|
||||
|
||||
+93
-13
@@ -43,6 +43,24 @@ pub enum CharacterTable {
|
||||
/// Uses code page 437 (for non-ASCII bytes).
|
||||
#[value(name = "codepage-437")]
|
||||
CP437,
|
||||
|
||||
/// Uses braille characters for non-printable bytes.
|
||||
Braille,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, ValueEnum)]
|
||||
#[non_exhaustive]
|
||||
pub enum ColorScheme {
|
||||
/// Show the default colors: bright black for NULL bytes, green for ASCII
|
||||
/// space characters and non-printable ASCII, cyan for printable ASCII characters,
|
||||
/// and yellow for non-ASCII bytes.
|
||||
#[default]
|
||||
Default,
|
||||
|
||||
/// Show bright black for NULL bytes, cyan for printable ASCII characters, a gradient
|
||||
/// from pink to violet for non-printable ASCII characters and a heatmap-like gradient
|
||||
/// from red to yellow to white for non-ASCII bytes.
|
||||
Gradient,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, ValueEnum)]
|
||||
@@ -81,14 +99,29 @@ impl Byte {
|
||||
}
|
||||
}
|
||||
|
||||
fn color(self) -> &'static [u8] {
|
||||
fn color(self, color_scheme: ColorScheme) -> &'static [u8] {
|
||||
use crate::ByteCategory::*;
|
||||
match self.category() {
|
||||
Null => COLOR_NULL.as_bytes(),
|
||||
AsciiPrintable => COLOR_ASCII_PRINTABLE.as_bytes(),
|
||||
AsciiWhitespace => COLOR_ASCII_WHITESPACE.as_bytes(),
|
||||
AsciiOther => COLOR_ASCII_OTHER.as_bytes(),
|
||||
NonAscii => COLOR_NONASCII.as_bytes(),
|
||||
match color_scheme {
|
||||
ColorScheme::Default => match self.category() {
|
||||
Null => COLOR_NULL.as_bytes(),
|
||||
AsciiPrintable => COLOR_ASCII_PRINTABLE.as_bytes(),
|
||||
AsciiWhitespace => COLOR_ASCII_WHITESPACE.as_bytes(),
|
||||
AsciiOther => COLOR_ASCII_OTHER.as_bytes(),
|
||||
NonAscii => COLOR_NONASCII.as_bytes(),
|
||||
},
|
||||
ColorScheme::Gradient => match self.category() {
|
||||
Null => COLOR_NULL_RGB,
|
||||
AsciiWhitespace if self.0 == b' ' => &COLOR_GRADIENT_ASCII_PRINTABLE[0],
|
||||
AsciiPrintable => &COLOR_GRADIENT_ASCII_PRINTABLE[(self.0 - b' ') as usize],
|
||||
AsciiWhitespace | AsciiOther => {
|
||||
if self.0 == 0x7f {
|
||||
COLOR_DEL
|
||||
} else {
|
||||
&COLOR_GRADIENT_ASCII_NONPRINTABLE[self.0 as usize - 1]
|
||||
}
|
||||
}
|
||||
NonAscii => &COLOR_GRADIENT_NONASCII[(self.0 - 128) as usize],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +146,37 @@ impl Byte {
|
||||
},
|
||||
CharacterTable::CP1047 => CP1047[self.0 as usize],
|
||||
CharacterTable::CP437 => CP437[self.0 as usize],
|
||||
CharacterTable::Braille => match self.category() {
|
||||
// null is important enough to get its own symbol
|
||||
Null => '⋄',
|
||||
AsciiPrintable => self.0 as char,
|
||||
AsciiWhitespace if self.0 == b' ' => ' ',
|
||||
// `\t`, `\n` and `\r` are important enough to get their own symbols
|
||||
AsciiWhitespace if self.0 == b'\t' => '→',
|
||||
AsciiWhitespace if self.0 == b'\n' => '↵',
|
||||
AsciiWhitespace if self.0 == b'\r' => '←',
|
||||
AsciiWhitespace | AsciiOther | NonAscii => {
|
||||
/// Adjust the bits from the original number to a new number.
|
||||
///
|
||||
/// Bit positions in braille are adjusted as follows:
|
||||
///
|
||||
/// ```text
|
||||
/// 0 3 => 0 1
|
||||
/// 1 4 => 2 3
|
||||
/// 2 5 => 4 5
|
||||
/// 6 7 => 6 7
|
||||
/// ```
|
||||
fn to_braille_bits(byte: u8) -> u8 {
|
||||
let mut out = 0;
|
||||
for (from, to) in [0, 3, 1, 4, 2, 5, 6, 7].into_iter().enumerate() {
|
||||
out |= (byte >> from & 1) << to;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
char::from_u32(0x2800 + to_braille_bits(self.0) as u32).unwrap()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,6 +267,7 @@ pub struct PrinterBuilder<'a, Writer: Write> {
|
||||
base: Base,
|
||||
endianness: Endianness,
|
||||
character_table: CharacterTable,
|
||||
color_scheme: ColorScheme,
|
||||
}
|
||||
|
||||
impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
|
||||
@@ -219,6 +284,7 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
|
||||
base: Base::Hexadecimal,
|
||||
endianness: Endianness::Big,
|
||||
character_table: CharacterTable::Default,
|
||||
color_scheme: ColorScheme::Default,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,6 +338,11 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn color_scheme(mut self, color_scheme: ColorScheme) -> Self {
|
||||
self.color_scheme = color_scheme;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Printer<'a, Writer> {
|
||||
Printer::new(
|
||||
self.writer,
|
||||
@@ -285,6 +356,7 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
|
||||
self.base,
|
||||
self.endianness,
|
||||
self.character_table,
|
||||
self.color_scheme,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -298,6 +370,7 @@ pub struct Printer<'a, Writer: Write> {
|
||||
show_position_panel: bool,
|
||||
show_color: bool,
|
||||
curr_color: Option<&'static [u8]>,
|
||||
color_scheme: ColorScheme,
|
||||
border_style: BorderStyle,
|
||||
byte_hex_panel: Vec<String>,
|
||||
byte_char_panel: Vec<String>,
|
||||
@@ -329,6 +402,7 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
||||
base: Base,
|
||||
endianness: Endianness,
|
||||
character_table: CharacterTable,
|
||||
color_scheme: ColorScheme,
|
||||
) -> Printer<'a, Writer> {
|
||||
Printer {
|
||||
idx: 0,
|
||||
@@ -338,6 +412,7 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
||||
show_position_panel,
|
||||
show_color,
|
||||
curr_color: None,
|
||||
color_scheme,
|
||||
border_style,
|
||||
byte_hex_panel: (0u8..=u8::MAX)
|
||||
.map(|i| match base {
|
||||
@@ -481,9 +556,10 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
||||
Squeezer::Print | Squeezer::Delete => self.writer.write_all(b" ")?,
|
||||
Squeezer::Ignore | Squeezer::Disabled => {
|
||||
if let Some(&b) = self.line_buf.get(i as usize) {
|
||||
if self.show_color && self.curr_color != Some(Byte(b).color()) {
|
||||
self.writer.write_all(Byte(b).color())?;
|
||||
self.curr_color = Some(Byte(b).color());
|
||||
if self.show_color && self.curr_color != Some(Byte(b).color(self.color_scheme))
|
||||
{
|
||||
self.writer.write_all(Byte(b).color(self.color_scheme))?;
|
||||
self.curr_color = Some(Byte(b).color(self.color_scheme));
|
||||
}
|
||||
self.writer
|
||||
.write_all(self.byte_char_panel[b as usize].as_bytes())?;
|
||||
@@ -550,9 +626,9 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
||||
if i % (self.group_size as usize) == 0 {
|
||||
self.writer.write_all(b" ")?;
|
||||
}
|
||||
if self.show_color && self.curr_color != Some(Byte(b).color()) {
|
||||
self.writer.write_all(Byte(b).color())?;
|
||||
self.curr_color = Some(Byte(b).color());
|
||||
if self.show_color && self.curr_color != Some(Byte(b).color(self.color_scheme)) {
|
||||
self.writer.write_all(Byte(b).color(self.color_scheme))?;
|
||||
self.curr_color = Some(Byte(b).color(self.color_scheme));
|
||||
}
|
||||
self.writer
|
||||
.write_all(self.byte_hex_panel[b as usize].as_bytes())?;
|
||||
@@ -780,6 +856,7 @@ mod tests {
|
||||
Base::Hexadecimal,
|
||||
Endianness::Big,
|
||||
CharacterTable::Default,
|
||||
ColorScheme::Default,
|
||||
);
|
||||
|
||||
printer.print_all(input).unwrap();
|
||||
@@ -836,6 +913,7 @@ mod tests {
|
||||
Base::Hexadecimal,
|
||||
Endianness::Big,
|
||||
CharacterTable::Default,
|
||||
ColorScheme::Default,
|
||||
);
|
||||
printer.display_offset(0xdeadbeef);
|
||||
|
||||
@@ -871,6 +949,7 @@ mod tests {
|
||||
Base::Hexadecimal,
|
||||
Endianness::Big,
|
||||
CharacterTable::Default,
|
||||
ColorScheme::Default,
|
||||
);
|
||||
|
||||
printer.print_all(input).unwrap();
|
||||
@@ -932,6 +1011,7 @@ mod tests {
|
||||
Base::Hexadecimal,
|
||||
Endianness::Big,
|
||||
CharacterTable::Default,
|
||||
ColorScheme::Default,
|
||||
);
|
||||
|
||||
printer.print_all(input).unwrap();
|
||||
|
||||
+8
-1
@@ -14,7 +14,7 @@ use thiserror::Error as ThisError;
|
||||
|
||||
use terminal_size::terminal_size;
|
||||
|
||||
use hexyl::{Base, BorderStyle, CharacterTable, Endianness, Input, PrinterBuilder};
|
||||
use hexyl::{Base, BorderStyle, CharacterTable, ColorScheme, Endianness, Input, PrinterBuilder};
|
||||
|
||||
use hexyl::{
|
||||
COLOR_ASCII_OTHER, COLOR_ASCII_PRINTABLE, COLOR_ASCII_WHITESPACE, COLOR_NONASCII, COLOR_NULL,
|
||||
@@ -129,6 +129,10 @@ struct Opt {
|
||||
#[arg(long, value_enum, default_value_t, value_name("FORMAT"))]
|
||||
character_table: CharacterTable,
|
||||
|
||||
/// Defines the color scheme for the characters.
|
||||
#[arg(long, value_enum, default_value_t, value_name("FORMAT"))]
|
||||
color_scheme: ColorScheme,
|
||||
|
||||
/// Whether to display the position panel on the left.
|
||||
#[arg(short('P'), long)]
|
||||
no_position: bool,
|
||||
@@ -429,6 +433,8 @@ fn run() -> Result<()> {
|
||||
|
||||
let character_table = opt.character_table;
|
||||
|
||||
let color_scheme = opt.color_scheme;
|
||||
|
||||
let mut stdout = BufWriter::new(io::stdout().lock());
|
||||
|
||||
let mut printer = PrinterBuilder::new(&mut stdout)
|
||||
@@ -442,6 +448,7 @@ fn run() -> Result<()> {
|
||||
.with_base(base)
|
||||
.endianness(endianness)
|
||||
.character_table(character_table)
|
||||
.color_scheme(color_scheme)
|
||||
.build();
|
||||
printer.display_offset(skip_offset + display_offset);
|
||||
printer.print_all(&mut reader).map_err(|e| anyhow!(e))?;
|
||||
|
||||
Reference in new issue
Block a user