use crate::ascii::ascii_prefix_len_capped;
use crate::euc_cn;
use crate::index;
use crate::result::{DecoderResult, EncoderResult};
use crate::sink::{ByteSink, DECODER_HEADROOM, ENCODER_HEADROOM, Pushback};
use crate::tables::cns11643::{
CNS_PLANE1_DECODE, CNS_PLANE1_ENCODE_BUCKETS, CNS_PLANE1_ENCODE_CODE_POINTS,
CNS_PLANE1_ENCODE_POINTERS, CNS_PLANE2_DECODE, CNS_PLANE2_ENCODE_BUCKETS,
CNS_PLANE2_ENCODE_CODE_POINTS, CNS_PLANE2_ENCODE_POINTERS,
};
const SO: u8 = 0x0E;
const SI: u8 = 0x0F;
const ESC: u8 = 0x1B;
const DESIGNATE_GB2312: [u8; 4] = [ESC, 0x24, 0x29, 0x41];
const DESIGNATE_CNS1: [u8; 4] = [ESC, 0x24, 0x29, 0x47];
const DESIGNATE_CNS2: [u8; 4] = [ESC, 0x24, 0x2A, 0x48];
const SS2: [u8; 2] = [ESC, 0x4E];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
enum G1 {
#[default]
None,
Gb2312,
Cns1,
}
#[inline]
fn g1_code_point(set: G1, lead: u8, trail: u8) -> Option<u32> {
match set {
G1::None => None,
G1::Gb2312 => euc_cn::code_point(lead | 0x80, trail | 0x80),
G1::Cns1 => index::code_point(
&CNS_PLANE1_DECODE,
(usize::from(lead) - 0x21) * 94 + (usize::from(trail) - 0x21),
),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum State {
Ground,
Trail(u8),
TrailSs2(u8),
Ss2,
Escape(u8),
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Iso2022CnDecoder {
state: State,
g1: G1,
g2: bool,
shifted: bool,
seen: [u8; 3],
pending: Pushback,
}
impl Default for Iso2022CnDecoder {
fn default() -> Self {
Iso2022CnDecoder {
state: State::Ground,
g1: G1::None,
g2: false,
shifted: false,
seen: [0; 3],
pending: Pushback::default(),
}
}
}
const ESCAPES: [&[u8]; 4] = [&SS2, &DESIGNATE_GB2312, &DESIGNATE_CNS1, &DESIGNATE_CNS2];
impl Iso2022CnDecoder {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn decode(
&mut self,
src: &[u8],
sink: &mut ByteSink,
last: bool,
) -> (DecoderResult, usize) {
let mut read = 0usize;
loop {
if !sink.has_room(DECODER_HEADROOM) {
return (DecoderResult::OutputFull, read);
}
if self.state == State::Ground && !self.shifted && self.pending.is_empty() {
let rest = &src[read..];
let run = ascii_prefix_len_capped(rest, sink.room());
let run = rest[..run]
.iter()
.position(|&b| b == SO || b == SI || b == ESC || b == b'\n')
.unwrap_or(run);
if run > 0 {
sink.write_slice(&rest[..run]);
read += run;
continue;
}
}
let (byte, from_pending) = match self.pending.next() {
Some(byte) => (Some(byte), true),
None => {
let byte = src.get(read).copied();
if byte.is_some() {
read += 1;
}
(byte, false)
}
};
if byte.is_none() && !last {
return (DecoderResult::InputEmpty, read);
}
match (self.state, byte) {
(State::Ground, None) => return (DecoderResult::InputEmpty, read),
(State::Trail(_) | State::TrailSs2(_) | State::Ss2, None) => {
self.state = State::Ground;
return (DecoderResult::Malformed(1), read);
}
(State::Escape(matched), None) => {
self.restore_escape(usize::from(matched));
return (DecoderResult::Malformed(1), read);
}
(State::Escape(matched), Some(b)) => {
let matched = usize::from(matched);
self.seen[matched] = b;
let after = &self.seen[..matched + 1];
let Some(escape) = ESCAPES.iter().find(|escape| escape[1..].starts_with(after))
else {
if from_pending {
self.pending.unread();
} else {
read -= 1;
}
self.restore_escape(matched);
return (DecoderResult::Malformed(1), read);
};
if after.len() + 1 < escape.len() {
self.state = State::Escape(matched as u8 + 1);
continue;
}
self.state = State::Ground;
if *escape == DESIGNATE_GB2312 {
self.g1 = G1::Gb2312;
} else if *escape == DESIGNATE_CNS1 {
self.g1 = G1::Cns1;
} else if *escape == DESIGNATE_CNS2 {
self.g2 = true;
} else if self.g2 {
self.state = State::Ss2;
} else {
return (DecoderResult::Malformed(2), read);
}
}
(State::Ss2, Some(b @ 0x21..=0x7E)) => self.state = State::TrailSs2(b),
(State::Ss2, Some(_)) => {
self.state = State::Ground;
return (DecoderResult::Malformed(1), read);
}
(State::Trail(lead), Some(b @ 0x21..=0x7E)) => {
self.state = State::Ground;
match g1_code_point(self.g1, lead, b) {
Some(code_point) => sink.write_code_point(code_point),
None => return (DecoderResult::Malformed(2), read),
}
}
(State::TrailSs2(lead), Some(b @ 0x21..=0x7E)) => {
self.state = State::Ground;
let pointer = (usize::from(lead) - 0x21) * 94 + (usize::from(b) - 0x21);
match index::code_point(&CNS_PLANE2_DECODE, pointer) {
Some(code_point) => sink.write_code_point(code_point),
None => return (DecoderResult::Malformed(2), read),
}
}
(State::Trail(_) | State::TrailSs2(_), Some(_)) => {
self.state = State::Ground;
return (DecoderResult::Malformed(2), read);
}
(State::Ground, Some(ESC)) => self.state = State::Escape(0),
(State::Ground, Some(SO)) => {
if self.g1 == G1::None {
return (DecoderResult::Malformed(1), read);
}
self.shifted = true;
}
(State::Ground, Some(SI)) => self.shifted = false,
(State::Ground, Some(b @ b'\n')) => {
self.shifted = false;
self.g1 = G1::None;
self.g2 = false;
sink.write_byte(b);
}
(State::Ground, Some(b)) if !self.shifted && b.is_ascii() => sink.write_byte(b),
(State::Ground, Some(b @ 0x21..=0x7E)) if self.shifted => {
self.state = State::Trail(b);
}
(State::Ground, Some(_)) => return (DecoderResult::Malformed(1), read),
}
}
}
fn restore_escape(&mut self, matched: usize) {
self.state = State::Ground;
if matched > 0 {
self.pending.set(&self.seen[..matched]);
}
}
}
enum Target {
Ascii(u8),
G1(G1, u16),
G2(u16),
}
fn target_of(c: char) -> Option<Target> {
let scalar = u32::from(c);
if c.is_ascii() {
return Some(Target::Ascii(scalar as u8));
}
if let Some((lead, trail)) = euc_cn::bytes(scalar) {
let pointer = u16::from(lead - 0xA1) * 94 + u16::from(trail - 0xA1);
return Some(Target::G1(G1::Gb2312, pointer));
}
if let Some(pointer) = index::pointer(
&CNS_PLANE1_ENCODE_CODE_POINTS,
&CNS_PLANE1_ENCODE_POINTERS,
&CNS_PLANE1_ENCODE_BUCKETS,
scalar,
) {
return Some(Target::G1(G1::Cns1, pointer));
}
index::pointer(
&CNS_PLANE2_ENCODE_CODE_POINTS,
&CNS_PLANE2_ENCODE_POINTERS,
&CNS_PLANE2_ENCODE_BUCKETS,
scalar,
)
.map(Target::G2)
}
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct Iso2022CnEncoder {
g1: G1,
g2: bool,
shifted: bool,
}
impl Iso2022CnEncoder {
pub(crate) fn encode(
&mut self,
src: &str,
sink: &mut ByteSink,
last: bool,
) -> (EncoderResult, usize) {
let mut read = 0usize;
loop {
if !sink.has_room(ENCODER_HEADROOM) {
return (EncoderResult::OutputFull, read);
}
let rest = &src[read..];
let Some(c) = rest.chars().next() else {
if last && self.shifted {
sink.write_byte(SI);
self.shifted = false;
}
return (EncoderResult::InputEmpty, read);
};
let width = c.len_utf8();
if matches!(u32::from(c), 0x0E | 0x0F | 0x1B) {
read += width;
return (EncoderResult::Unmappable(char::REPLACEMENT_CHARACTER), read);
}
let Some(target) = target_of(c) else {
if self.shifted {
sink.write_byte(SI);
self.shifted = false;
continue;
}
read += width;
return (EncoderResult::Unmappable(c), read);
};
match target {
Target::Ascii(byte) => {
if self.shifted {
sink.write_byte(SI);
self.shifted = false;
continue;
}
sink.write_byte(byte);
read += width;
if byte == b'\n' {
self.g1 = G1::None;
self.g2 = false;
}
}
Target::G1(set, pointer) => {
if self.g1 != set {
if self.shifted {
sink.write_byte(SI);
self.shifted = false;
continue;
}
sink.write_slice(match set {
G1::Cns1 => &DESIGNATE_CNS1,
_ => &DESIGNATE_GB2312,
});
self.g1 = set;
continue;
}
if !self.shifted {
sink.write_byte(SO);
self.shifted = true;
continue;
}
sink.write_slice(&[(pointer / 94) as u8 + 0x21, (pointer % 94) as u8 + 0x21]);
read += width;
}
Target::G2(pointer) => {
if !self.g2 {
sink.write_slice(&DESIGNATE_CNS2);
self.g2 = true;
continue;
}
sink.write_slice(&SS2);
sink.write_slice(&[(pointer / 94) as u8 + 0x21, (pointer % 94) as u8 + 0x21]);
read += width;
}
}
}
}
}