tauri-plugin-thermal-printer 1.3.2

Plugin for Tauri to send esc/pos commands to thermal_printer
Documentation
use crate::commands_esc_pos::text::code_page::CodePage;
use unicode_width::UnicodeWidthChar;

use super::accent_remover::accent_remover_bytes;
use super::encode::Encode;

#[derive(Debug, Clone)]
pub struct TextEncoder {
    encode: Encode,
    use_gbk: bool,
}

#[derive(Debug, Clone)]
pub(crate) struct EncodedChar {
    pub(crate) bytes: Vec<u8>,
    pub(crate) width: usize,
}

impl TextEncoder {
    pub fn from_code_page(code_page: &CodePage) -> Self {
        Self {
            encode: code_page.encode,
            use_gbk: code_page.use_gbk,
        }
    }

    pub fn encode_text(&self, text: &str) -> Result<Vec<u8>, String> {
        let mut output = Vec::with_capacity(text.len());

        for ch in text.chars() {
            output.extend_from_slice(&self.encode_char(ch)?.bytes);
        }

        Ok(output)
    }

    pub(crate) fn encode_char(&self, ch: char) -> Result<EncodedChar, String> {
        match self.encode.encoding() {
            Some(encoding) => self.encode_with_encoding(ch, encoding),
            None => self.encode_accent_remover(ch),
        }
    }

    fn encode_accent_remover(&self, ch: char) -> Result<EncodedChar, String> {
        if let Some(mapped) = accent_remover_bytes(ch) {
            return Ok(multi_byte_char(mapped));
        }

        if ch.is_ascii() {
            return Ok(single_byte_char(ch as u8));
        }

        self.encode_with_optional_gbk(ch)
    }

    fn encode_with_encoding(
        &self,
        ch: char,
        encoding: &'static encoding_rs::Encoding,
    ) -> Result<EncodedChar, String> {
        let value = ch.to_string();
        let (encoded, _, had_errors) = encoding.encode(&value);

        if had_errors {
            return self.encode_with_optional_gbk(ch);
        }

        Ok(EncodedChar {
            bytes: encoded.into_owned(),
            width: display_width(ch),
        })
    }

    fn encode_with_optional_gbk(&self, ch: char) -> Result<EncodedChar, String> {
        if !self.use_gbk {
            return Ok(passthrough_char(ch));
        }

        encode_with_gbk(ch)
    }
}

fn single_byte_char(byte: u8) -> EncodedChar {
    EncodedChar {
        bytes: vec![byte],
        width: 1,
    }
}

fn multi_byte_char(bytes: &'static [u8]) -> EncodedChar {
    EncodedChar {
        bytes: bytes.to_vec(),
        width: bytes.len(),
    }
}

fn encode_with_gbk(ch: char) -> Result<EncodedChar, String> {
    let value = ch.to_string();
    let (encoded, _, had_errors) = encoding_rs::GBK.encode(&value);

    if had_errors {
        return Ok(passthrough_char(ch));
    }

    Ok(EncodedChar {
        bytes: encoded.into_owned(),
        width: display_width(ch),
    })
}

fn passthrough_char(ch: char) -> EncodedChar {
    EncodedChar {
        bytes: ch.to_string().into_bytes(),
        width: display_width(ch),
    }
}

fn display_width(ch: char) -> usize {
    UnicodeWidthChar::width_cjk(ch)
        .or_else(|| UnicodeWidthChar::width(ch))
        .unwrap_or(1)
        .max(1)
}