use image;
use std::{fs, path::Path, io, ffi::OsStr};
use lazy_static::lazy_static;
use crate::{add_limited, bgra_management::*, PixelValues, BGRA_INVISIBLE_PIXEL};
pub use crate::PixelsCollection;
pub fn char_collection_from_image(buffer :&Vec<u8>, height :usize, mut start_x :usize, start_y :usize, mut range_x :usize, range_y :usize, min_px_space_btwn_chars :usize, chars_string :&str, space_char_width :u32, bgra_matcher :fn(u8,u8,u8,u8) -> bool) -> Result<CharsCollection<u8>, String> {
let img_visible_range = get_cardinal_points_until_nonestreak_x(&buffer, height, start_x, start_y, range_x, range_y, range_x, bgra_matcher);
let original_range_x = range_x;
let mut char_u8_vec = CharsCollection { chars: Vec::new(), path : "".to_string(), bgra : BGRA(0,0,0,255)};
for char in chars_string.chars() {
let values = get_cardinal_points_until_nonestreak_x(&buffer, height, start_x, start_y, range_x, range_y, min_px_space_btwn_chars, bgra_matcher);
let (pixels_captured, _) = pixel_grabber(&buffer, height, values.left_x, img_visible_range.top_y, values.right_x - values.left_x+1, values.bottom_y - img_visible_range.top_y+1, bgra_matcher);
char_u8_vec.chars.push(PixelsChar {char, char_name: CHARS.get_char_name_by_char(char).unwrap(), pixels: PixelsCollection::<u8>::create(values.right_x-values.left_x+1, values.bottom_y-img_visible_range.top_y+1, pixels_captured).unwrap() });
if char == chars_string.chars().last().unwrap() {
break;
}
start_x = values.right_x + min_px_space_btwn_chars;
if start_x > original_range_x {
return Err("Could not retrieve all the characters".to_owned());
}
range_x = original_range_x - start_x;
}
if char_u8_vec.chars.iter().find(|r| r.char == ' ').is_none() {
char_u8_vec.chars.push(PixelsChar {char: ' ', char_name: CHARS.get_char_name_by_char(' ').unwrap(), pixels: PixelsCollection::<u8>::create(space_char_width as usize, char_u8_vec.chars[0].pixels.height, vec![0; space_char_width as usize * char_u8_vec.chars[0].pixels.height as usize * 4]).unwrap() });
}
return Ok(char_u8_vec);
}
#[cfg(test)]
mod tests {
use crate::{pixels_string::*, PixelsSendMode};
const DISPLAY_RESULTS :bool = true;
#[test]
fn string_of_chars() {
let image_transparent_bkgrnd = PixelsCollection::<u8>::from_png("fonts/exports/transparent_green_40px_chars_sample__transparent_background.png").unwrap();
let buffer = PixelsCollection::white_background_to_transparency_gradient(&image_transparent_bkgrnd.bytes);
let height = image_transparent_bkgrnd.height;
let mut start_x = 0;
let start_y = 0;
let mut range_x = image_transparent_bkgrnd.width;
let range_y = image_transparent_bkgrnd.height;
let min_px_space_btwn_chars = 10;
let chars_string = r#"abcdefghijklmnopqrstuvwxyz,.?!01234567890-+/*\_@#()[]{};:"£$%&='^"#;
let space_char_width = 0;
let img_visible_range = get_cardinal_points_until_nonestreak_x(&buffer, height, start_x, start_y, range_x, range_y, range_x, |_:u8,_:u8,_:u8,a:u8| -> bool { a > 0});
let original_range_x = range_x;
if DISPLAY_RESULTS {
let mut buffer_alpha_not_zero = buffer.clone();
crate::Screen::update_area_custom(&mut buffer_alpha_not_zero, 0, 0, original_range_x as u32, range_y as u32, PixelsSendMode::AlphaEnabled);
image::save_buffer_with_format(format!("{}{}", "fonts/exports/", "testing_result_export.png"), &<u8>::swap_blue_with_red(&buffer_alpha_not_zero), range_x as u32, range_y as u32, image::ColorType::Rgba8, image::ImageFormat::Png).unwrap();
}
let mut char_u8_vec: CharsCollection<u8> = CharsCollection { chars: Vec::new(), path : "".to_string(), bgra : BGRA(0,0,0,255)};
let mut bytes_chars_poles = buffer.clone();
for char in chars_string.chars() {
let values = get_cardinal_points_until_nonestreak_x(&buffer, height, start_x, start_y, range_x, range_y, min_px_space_btwn_chars, |_:u8,_:u8,_:u8,a:u8| -> bool { a > 0});
let (mut pixels_captured, char_values) = pixel_grabber(&buffer, height, values.left_x, img_visible_range.top_y, values.right_x - values.left_x+1, values.bottom_y - img_visible_range.top_y+1, |_:u8,_:u8,_:u8,a:u8| -> bool { a > 0});
if DISPLAY_RESULTS {
let vec_pos_char = vec![(values.left_x, img_visible_range.top_y), (values.left_x, img_visible_range.bottom_y), (values.right_x, img_visible_range.top_y), (values.right_x, img_visible_range.bottom_y)];
bytes_chars_poles.set_positions_bgra(height, &vec_pos_char, 0, 255, 0, 255);
let vec_pos_char_strict = vec![(values.left_x, values.top_y), (values.left_x, values.bottom_y), (values.right_x, values.top_y), (values.right_x, values.bottom_y)];
bytes_chars_poles.set_positions_bgra(height, &vec_pos_char_strict, 170, 255, 170, 255);
let vec_addresses_char = vec![values.top_y_index, values.left_x_index, values.right_x_index, values.bottom_y_index];
bytes_chars_poles.set_addresses_bgra(&vec_addresses_char, 0, 0, 255, 255);
crate::Screen::update_area_custom(&bytes_chars_poles, 0, (range_y + 10) as i32, original_range_x as u32, range_y as u32, PixelsSendMode::AlphaEnabled);
crate::Screen::update_area_custom(&mut pixels_captured, 0, ((range_y + 10)*2) as i32, (values.right_x - values.left_x) as u32 +1, (values.bottom_y - img_visible_range.top_y) as u32 +1, PixelsSendMode::AlphaEnabled);
let mut pixels_captured_clone = pixels_captured.clone();
let vec_pos_char_relative = vec![
(0, 0),
(0, values.bottom_y - img_visible_range.top_y),
(char_values.right_x, 0),
(char_values.right_x, values.bottom_y - img_visible_range.top_y)
];
pixels_captured_clone.set_positions_bgra((values.bottom_y - img_visible_range.top_y)+1, &vec_pos_char_relative, 170, 255, 170, 255);
let vec_addresses_char_relative = vec![char_values.top_y_index, char_values.left_x_index, char_values.right_x_index, char_values.bottom_y_index];
pixels_captured_clone.set_addresses_bgra(&vec_addresses_char_relative, 0, 0, 255, 255);
crate::Screen::update_area_custom(&mut pixels_captured_clone, 0, ((range_y + 10)*3) as i32,(values.right_x - values.left_x) as u32 +1, (values.bottom_y - img_visible_range.top_y) as u32 +1, PixelsSendMode::AlphaEnabled);
}
char_u8_vec.chars.push(PixelsChar {char, char_name: String::from(char), pixels: PixelsCollection::<u8>::create(values.right_x - values.left_x+1, values.bottom_y - img_visible_range.top_y+1, pixels_captured).unwrap() });
if char == chars_string.chars().last().unwrap() {
break;
}
start_x = values.right_x + min_px_space_btwn_chars;
if start_x > original_range_x {
break;
}
range_x = original_range_x - start_x;
}
if DISPLAY_RESULTS {
let vec_pos_string = vec![(img_visible_range.left_x, img_visible_range.top_y), (img_visible_range.left_x, img_visible_range.bottom_y), (img_visible_range.right_x, img_visible_range.top_y), (img_visible_range.right_x, img_visible_range.bottom_y)];
bytes_chars_poles.set_positions_bgra(height, &vec_pos_string, 255, 255, 0, 255);
let vec_addresses_string = vec![img_visible_range.top_y_index, img_visible_range.left_x_index, img_visible_range.right_x_index, img_visible_range.bottom_y_index];
bytes_chars_poles.set_addresses_bgra(&vec_addresses_string, 255, 0, 255, 255);
crate::Screen::update_area_custom(&bytes_chars_poles, 0, ((range_y + 10)*4) as i32, original_range_x as u32, range_y as u32, PixelsSendMode::AlphaEnabled);
}
if char_u8_vec.chars.iter().find(|r| r.char == ' ').is_none() {
char_u8_vec.chars.push(PixelsChar {char: ' ', char_name: String::from("space"), pixels: PixelsCollection::<u8>::create(space_char_width, char_u8_vec.chars[0].pixels.height, vec![0; space_char_width as usize * char_u8_vec.chars[0].pixels.height as usize * 4]).unwrap() });
}
assert_eq!(
char_u8_vec.chars.len(), chars_string.len(),
"Could not retrieve all the characters ({}/{} retrieved)",
char_u8_vec.chars.len(), chars_string.len()
);
let strings_from_string_png = Vec::from([
char_u8_vec.create_pixels_string("testing generated_text!^", 3)
]);
crate::Screen::update_area_custom(&strings_from_string_png[0].pixels.bytes, 0, ((range_y + 10)*5) as i32, strings_from_string_png[0].pixels.width as u32, strings_from_string_png[0].pixels.height as u32, PixelsSendMode::AlphaEnabled);
}
#[test]
fn hashmap_test() {
assert_eq!(find_key_for_value(&CHARS, 'a'), Some("LATIN SMALL LETTER A".to_string()));
assert_eq!(find_key_for_value(&CHARS, 'A'), Some("LATIN CAPITAL LETTER A".to_string()));
assert_eq!(CHARS.get_char_by_char_name_with_default("DIGIT ZERO"), '0');
assert_eq!(CHARS.get_char_by_char_name_with_default("banana"), '█');
}
}
pub struct CardinalPoints {
pub top_y :usize,
pub top_y_index :usize,
pub right_x :usize,
pub right_x_index :usize,
pub left_x :usize,
pub left_x_index :usize,
pub bottom_y :usize,
pub bottom_y_index :usize
}
pub fn get_cardinal_points_until_nonestreak_x(buffer :&Vec<u8>, height :usize, start_x :usize, start_y :usize, range_x :usize, range_y :usize, none_streak_x :usize, bgra_matcher :fn(u8,u8,u8,u8) -> bool) -> CardinalPoints {
let stride = buffer.len() / height;
let mut values = CardinalPoints {
top_y : start_y+range_y,
top_y_index : 0,
right_x : 0,
right_x_index : 0,
left_x : start_x+range_x,
left_x_index : 0,
bottom_y : 0,
bottom_y_index : 0
};
for x in start_x..start_x+range_x {
for y in start_y..start_y+range_y {
let i = stride * y + 4 * x;
if bgra_matcher (buffer[i], buffer[i+1], buffer[i+2], buffer[i+3]){
if y < values.top_y {
values.top_y = y;
values.top_y_index = i;
}
if x < values.left_x {
values.left_x = x;
values.left_x_index = i;
}
if x > values.right_x {
values.right_x = x;
values.right_x_index = i;
}
if y > values.bottom_y {
values.bottom_y = y;
values.bottom_y_index = i;
}
}
}
if values.right_x > start_x && x > values.right_x + none_streak_x {
break;
}
}
return values;
}
pub fn pixel_grabber(buffer :&Vec<u8>, height :usize, start_x :usize, start_y :usize, range_x :usize, range_y :usize, bgra_matcher :fn(u8,u8,u8,u8) -> bool) -> (Vec<u8>, CardinalPoints) {
let stride = buffer.len() / height;
let mut pixels_captured :Vec<u8> = Vec::with_capacity(range_x * range_y * 4);
let mut values = CardinalPoints {
top_y : start_y+range_y,
top_y_index : 0,
right_x : 0,
right_x_index : 0,
left_x : start_x+range_x,
left_x_index : 0,
bottom_y : 0,
bottom_y_index : 0
};
let mut rx = 0;
let mut ry = 0;
let rstride = (range_x * range_y * 4) / range_y;
for y in start_y..start_y+range_y {
for x in start_x..start_x+range_x {
let i = stride * y + 4 * x;
let j = rstride * ry + 4 * rx;
if bgra_matcher (buffer[i], buffer[i+1], buffer[i+2], buffer[i+3]){
pixels_captured.extend_from_slice(&[ buffer[i], buffer[i+1], buffer[i+2], buffer[i+3] ]);
if ry < values.top_y {
values.top_y = ry;
values.top_y_index = j;
}
if rx < values.left_x {
values.left_x = rx;
values.left_x_index = j;
}
if rx > values.right_x {
values.right_x = rx;
values.right_x_index = j;
}
if ry > values.bottom_y {
values.bottom_y = ry;
values.bottom_y_index = j;
}
}
else {
pixels_captured.extend_from_slice(&[ BGRA_INVISIBLE_PIXEL.0, BGRA_INVISIBLE_PIXEL.1, BGRA_INVISIBLE_PIXEL.2, BGRA_INVISIBLE_PIXEL.3]);
}
rx += 1;
}
rx = 0;
ry += 1;
}
return (pixels_captured, values);
}
impl PixelsCollection<u8> {
pub fn from_png(png_path: &str) -> Result<PixelsCollection<u8>,String> {
match crate::pixels_string::png_into_pixels_collection(png_path) {
Ok(mut pixel_coll) => {
pixel_coll.switch_bytes(0,2);
Ok(pixel_coll)
},
Err(err) => Err(err)
}
}
pub fn try_create_char_collection(&self, min_px_space_btwn_chars :usize, chars_string :&str, space_char_width :u32, bgra_matcher :fn(u8,u8,u8,u8) -> bool) -> Result<CharsCollection<u8>, String> {
let start_x = 0;
let start_y = 0;
let range_x = self.width;
let range_y = self.height;
match char_collection_from_image(&self.bytes, self.height, start_x, start_y, range_x, range_y, min_px_space_btwn_chars, chars_string, space_char_width, bgra_matcher) {
Ok(mut coll) => {
coll.bgra = image_lowest_visible_bgr(&self.bytes);
Ok(coll)
},
Err(err) => Err(err)
}
}
}
impl PixelsCollection<u32> {
pub fn from_png(png_path: &str) -> Result<PixelsCollection<u32>,String> {
match crate::pixels_string::png_into_pixels_collection(png_path) {
Ok(mut pixel_coll) => {
pixel_coll.switch_bytes(0,2);
Ok(PixelsCollection::<u32>::create(pixel_coll.width, pixel_coll.height, <u8>::u8_u32_casting(&pixel_coll.bytes))?)
},
Err(err) => Err(err)
}
}
}
pub fn png_into_pixels_collection (png_path :&str) -> Result<PixelsCollection<u8>,String> {
match image::open(png_path) {
Ok(img) => {
return Ok( PixelsCollection::<u8>::create(img.width() as usize, img.height() as usize, img.into_rgba8().to_vec())? );
},
Err(err) => {
return Err(err.to_string());
}
};
}
fn create_dir_recursive(dir_full_path : &str) -> std::io::Result<()> {
fs::create_dir_all(dir_full_path)?;
Ok(())
}
#[derive(Clone)]
pub struct PixelsChar<T: PixelValues<T>> {
pub char: char,
pub char_name: String,
pub pixels: PixelsCollection<T>
}
impl PixelsChar<u8> {
pub fn create(char: char, char_name: &str, width: usize, height: usize, bytes: Vec<u8>) -> Result<PixelsChar<u8>, String> {
Ok(PixelsChar {
char,
char_name: char_name.to_string(),
pixels: PixelsCollection::<u8>::create(width, height, bytes)?
})
}
pub fn from_png (png_path: &str, char: char, char_name: &str) -> Result<PixelsChar<u8>,String> {
match crate::pixels_string::png_into_pixels_collection(png_path) {
Ok(mut bytes) => {
bytes.switch_bytes(0,2);
Ok(PixelsChar { char, char_name: char_name.to_string(), pixels : bytes })
},
Err(err) => Err(err)
}
}
pub fn switch_bytes(&mut self, i1 :usize, i2 :usize) {
<u8>::switch_bytes(&mut self.pixels.bytes, i1, i2);
}
}
#[derive(Clone,Copy)]
pub struct BGRA<T: Copy + Clone> (pub T, pub T, pub T, pub T);
impl<T: Copy + Clone> BGRA<T> {
pub fn to_vec(&self) -> Vec<T> {
return vec![self.0,self.1,self.2,self.3]
}
}
#[derive(Clone)]
pub struct CharsCollection<T: PixelValues<T> + Copy + Clone> {
pub chars: Vec<PixelsChar<T>>,
pub path: String,
pub bgra: BGRA<T>
}
impl CharsCollection<u8> {
pub fn from_pngs_folder(dir: &str) -> io::Result<CharsCollection<u8>> {
let mut char_u8_vec = CharsCollection { chars: Vec::new(), path : dir.to_string(), bgra : BGRA(0,0,0,255)};
for entry in fs::read_dir(Path::new(dir))? {
let entry = entry?;
let path = entry.path();
if !path.is_dir() && path.extension().and_then(OsStr::to_str).unwrap() == "png" {
let fname_without_extension = String::from(path.file_stem().unwrap().to_str().unwrap());
let fpath = path.into_os_string().into_string().unwrap();
match PixelsChar::from_png(&fpath, CHARS.get_char_by_char_name_with_default(&fname_without_extension), &fname_without_extension) {
Ok(mut pixels_char) => {
pixels_char.switch_bytes(0,2);
char_u8_vec.chars.push(pixels_char);
},
Err(_) => ()
}
}
}
return Ok(char_u8_vec);
}
pub fn export_as_pngs (&self, folder_path: &str) -> std::io::Result<()> {
Self::export(&format!("{}", folder_path),&self)
}
pub fn export_as_pngs_overwrite_except_char (&mut self, folder_path: &str, c_to_exclude :char) -> std::io::Result<()> {
let mut cc_except = CharsCollection {chars : self.chars.clone(), path : self.path.to_string(), bgra : self.bgra.clone()};
cc_except.chars.retain(|x| x.char != c_to_exclude);
Self::export(&format!("{}{}", folder_path.trim_end_matches("\\"), "\\mapped_in_CHARS\\"),&cc_except)
}
fn export (png_path :&str, coll :&CharsCollection<u8>) -> std::io::Result<()> {
match create_dir_recursive(png_path) {
Ok(()) => {}
Err(err) => {
return Err(err);
}
};
for c in &coll.chars {
image::save_buffer_with_format(format!("{}{}.png", png_path.trim_end_matches("\\").to_owned() + "\\", c.char_name), &<u8>::swap_blue_with_red(&c.pixels.bytes), c.pixels.width as u32, c.pixels.height as u32, image::ColorType::Rgba8, image::ImageFormat::Png).unwrap();
}
Ok(())
}
}
impl CharsCollection<u8> {
pub fn grey_scale_into_black (&mut self, grey_threshold :u8) {
for v in &mut self.chars {
PixelsCollection::grey_scale_into_black(&mut v.pixels.bytes, grey_threshold);
}
}
pub fn matching_color_change (&mut self, b :u8, g :u8, r :u8, a : u8, new_b :u8, new_g :u8, new_r :u8, new_a :u8) {
for v in &mut self.chars{
v.pixels.bytes.color_matcher_and_new_color(|v0:u8,v1:u8,v2:u8,v3:u8| -> bool { v0 == b && v1 == g && v2 == r && v3 == a},new_b,new_g,new_r,new_a);
}
}
pub fn set_bgra_for_invisible (&mut self, b :u8, g :u8, r :u8, a : u8) {
self.chars.iter_mut().for_each(|c| {
c.pixels.set_bgra_for_invisible(b, g, r, a)
});
}
pub fn alpha_match_set_bgr (&mut self, a : u8, b :u8, g :u8, r :u8) {
self.chars.iter_mut().for_each(|x| x.pixels.bytes.color_matcher_and_new_color(|_:u8,_:u8,_:u8,v3:u8| -> bool {v3 == a},b,g,r,a));
self.bgra = BGRA(b,g,r,a);
}
pub fn set_bgr (&mut self, b :u8, g :u8, r :u8) {
self.chars.iter_mut().for_each(|x| x.pixels.bytes.set_bgr(b,g,r));
self.bgra = BGRA(b,g,r,self.bgra.3);
}
pub fn create_pixels_string (&self, string :&str, char_spacing :isize) -> PixelsString {
let mut vec :Vec<u8> = Vec::new();
let mut vec_width = 0;
let tallest_char_height = self.chars.iter().fold(1, |a,b| a.max(b.pixels.height));
let widest_char_width = self.chars.iter().fold(1, |a,b| a.max(b.pixels.width));
for string_y in 0..tallest_char_height {
for s in string.chars() {
let char_found = self.chars.iter().find(|r| r.char == s);
if let Some(char) = char_found {
let char_vec = &char.pixels.bytes;
let char_total_width = add_limited!(char.pixels.width as i32, char_spacing, 0);
if char.pixels.height <= string_y {
for _ in 0..char_total_width {
vec.extend_from_slice(&[0,0,0,0]);
}
continue;
}
for w in 0..char_total_width {
if w >= char.pixels.width as i32 {
vec.extend_from_slice(&[0,0,0,0]);
}
else {
let i = (char_vec.len()/char.pixels.height as usize) * string_y as usize + (w * 4) as usize;
vec.extend_from_slice(&[char_vec[i], char_vec[i+1], char_vec[i+2], char_vec[i+3]])
}
}
if string_y == 0 {
vec_width += char_total_width as usize;
}
}
else {
for _ in 0..add_limited!(widest_char_width as i32, char_spacing, widest_char_width as i32) {
vec.extend_from_slice(&[self.bgra.0,self.bgra.1,self.bgra.2,255]);
}
if string_y == 0 {
vec_width += add_limited!(widest_char_width as i32, char_spacing, widest_char_width as i32) as usize;
}
continue;
}
}
}
return PixelsString { bgra : self.bgra.clone(), pixels : PixelsCollection::<u8>::create(vec_width as usize, tallest_char_height as usize, vec).unwrap()}
}
}
#[derive(Clone)]
pub struct PixelsString {
pub bgra :BGRA<u8>,
pub pixels :PixelsCollection<u8>
}
fn find_key_for_value(map: &std::collections::HashMap<String, char>, value: char) -> Option<String> {
map.iter()
.find_map(|(key, &val)| if val == value { Some(key.to_owned()) } else { None })
}
trait CharsHashmap {
fn get_char_by_char_name_with_default(&self, char_name :&str) -> char;
fn get_char_name_by_char(&self, char :char) -> Option<String>;
}
impl CharsHashmap for std::collections::HashMap<String, char> {
fn get_char_by_char_name_with_default (&self, char_name :&str) -> char {
match self.get(char_name) {
Some(x) => *x,
None => '█'
}
}
fn get_char_name_by_char (&self, char :char) -> Option<String> {
find_key_for_value(&self, char)
}
}
lazy_static! {
static ref CHARS: std::collections::HashMap<String, char> = {
let mut chars = std::collections::HashMap::new();
for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars() {
chars.insert("LATIN CAPITAL LETTER ".to_string() + &c.to_string(), c);
chars.insert("LATIN SMALL LETTER ".to_string() + &c.to_string(), c.to_lowercase().collect::<Vec<_>>()[0]);
}
chars.insert("DIGIT ZERO".to_string(), '0');
chars.insert("DIGIT ONE".to_string(), '1');
chars.insert("DIGIT TWO".to_string(), '2');
chars.insert("DIGIT THREE".to_string(), '3');
chars.insert("DIGIT FOUR".to_string(), '4');
chars.insert("DIGIT FIVE".to_string(), '5');
chars.insert("DIGIT SIX".to_string(), '6');
chars.insert("DIGIT SEVEN".to_string(), '7');
chars.insert("DIGIT EIGHT".to_string(), '8');
chars.insert("DIGIT NINE".to_string(), '9');
chars.insert("AMPERSAND".to_string(), '&');
chars.insert("CIRCUMFLEX ACCENT".to_string(), '^');
chars.insert("ASTERISK".to_string(), '*');
chars.insert("REVERSE SOLIDUS".to_string(), '\\');
chars.insert("VERTICAL LINE".to_string(), '|');
chars.insert("LEFT CURLY BRACKET".to_string(), '{');
chars.insert("RIGHT CURLY BRACKET".to_string(), '}');
chars.insert("LEFT SQUARE BRACKET".to_string(), '[');
chars.insert("RIGHT SQUARE BRACKET".to_string(), ']');
chars.insert("COLON".to_string(), ':');
chars.insert("COMMA".to_string(), ',');
chars.insert("DEGREE SIGN".to_string(), '°');
chars.insert("DIVISION SIGN".to_string(), '÷');
chars.insert("EQUALS SIGN".to_string(), '=');
chars.insert("PERCENT SIGN".to_string(), '%');
chars.insert("EXCLAMATION MARK".to_string(), '!');
chars.insert("GREATER-THAN SIGN".to_string(), '>');
chars.insert("LESS-THAN SIGN".to_string(), '<');
chars.insert("HYPHEN-MINUS".to_string(), '-');
chars.insert("LEFT PARENTHESIS".to_string(), '(');
chars.insert("RIGHT PARENTHESIS".to_string(), ')');
chars.insert("AMPERSAND".to_string(), '&');
chars.insert("FULL STOP".to_string(), '.');
chars.insert("PLUS SIGN".to_string(), '+');
chars.insert("QUESTION MARK".to_string(), '?');
chars.insert("QUOTATION MARK".to_string(), '"');
chars.insert("APOSTROPHE".to_string(), '\'');
chars.insert("SEMICOLON".to_string(), ';');
chars.insert("SOLIDUS".to_string(), '/');
chars.insert("SPACE".to_string(), ' ');
chars.insert("LOW LINE".to_string(), '_');
chars.insert("COMMERCIAL AT".to_string(), '@');
chars.insert("NUMBER SIGN".to_string(), '#');
chars.insert("POUND SIGN".to_string(), '£');
chars.insert("DOLLAR SIGN".to_string(), '$');
chars.insert("EURO SIGN".to_string(), '€');
chars
};
}