use super::cjk::{Step, advance, cjk, push_value};
use super::{DecodeError, Errors, Iso2022Variant};
pub(super) fn decode_hz(data: &[u8], errors: Errors, out: &mut String) -> Result<(), DecodeError> {
let tables = cjk();
let gb2312 = &tables.iso2022["gb2312_7bit"];
out.reserve(data.len());
let mut position = 0usize;
let mut gb_mode = false;
while position < data.len() {
let rest = &data[position..];
let byte = rest[0];
let step = if byte == b'~' {
if rest.len() < 2 {
Step::Incomplete
} else {
match (rest[1], gb_mode) {
(b'~', false) => {
out.push('~');
Step::Ok(2)
}
(b'{', false) => {
gb_mode = true;
Step::Ok(2)
}
(b'\n', false) => Step::Ok(2),
(b'}', true) => {
gb_mode = false;
Step::Ok(2)
}
_ => Step::Error(1),
}
}
} else if byte & 0x80 != 0 {
Step::Error(1)
} else if !gb_mode {
out.push(byte as char);
Step::Ok(1)
} else if rest.len() < 2 {
Step::Incomplete
} else {
match gb2312.get(byte, rest[1]) {
Some(value) => {
push_value(out, value, &tables.pairs)?;
Step::Ok(2)
}
None => Step::Error(1),
}
};
if !advance(step, errors, &mut position)? {
break;
}
}
Ok(())
}
const DBCS: u8 = 0x80;
const CHARSET_ASCII: u8 = b'B';
const CHARSET_ISO8859_1: u8 = b'A';
const CHARSET_ISO8859_7: u8 = b'F';
#[derive(Clone, Copy)]
enum Designation {
Double(&'static str),
Single(&'static str),
Dummy,
}
struct Iso2022Config {
no_shift: bool,
use_g2: bool,
jisx0208_ext: bool,
designations: &'static [(u8, Designation)],
}
const JISX0208: (u8, Designation) = (b'B' | DBCS, Designation::Double("jisx0208"));
const JISX0208_O: (u8, Designation) = (b'@' | DBCS, Designation::Double("jisx0208"));
const JISX0212: (u8, Designation) = (b'D' | DBCS, Designation::Double("jisx0212"));
const KSX1001: (u8, Designation) = (b'C' | DBCS, Designation::Double("ksx1001"));
const GB2312: (u8, Designation) = (b'A' | DBCS, Designation::Double("gb2312_7bit"));
const JISX0201_R: (u8, Designation) = (b'J', Designation::Single("jisx0201_r"));
const JISX0201_K: (u8, Designation) = (b'I', Designation::Single("jisx0201_k"));
const ISO8859_1: (u8, Designation) = (CHARSET_ISO8859_1, Designation::Dummy);
const ISO8859_7: (u8, Designation) = (CHARSET_ISO8859_7, Designation::Dummy);
fn iso2022_config(variant: Iso2022Variant) -> Iso2022Config {
match variant {
Iso2022Variant::Kr => Iso2022Config {
no_shift: false,
use_g2: false,
jisx0208_ext: false,
designations: &[KSX1001],
},
Iso2022Variant::Jp => Iso2022Config {
no_shift: true,
use_g2: false,
jisx0208_ext: true,
designations: &[JISX0208, JISX0201_R, JISX0208_O],
},
Iso2022Variant::Jp1 => Iso2022Config {
no_shift: true,
use_g2: false,
jisx0208_ext: true,
designations: &[JISX0208, JISX0212, JISX0201_R, JISX0208_O],
},
Iso2022Variant::Jp2 => Iso2022Config {
no_shift: true,
use_g2: true,
jisx0208_ext: true,
designations: &[
JISX0208, JISX0212, KSX1001, GB2312, JISX0201_R, JISX0208_O, ISO8859_1, ISO8859_7,
],
},
Iso2022Variant::Jp2004 => Iso2022Config {
no_shift: true,
use_g2: false,
jisx0208_ext: true,
designations: &[
(b'Q' | DBCS, Designation::Double("jisx0213_2004_1")),
JISX0208,
(b'P' | DBCS, Designation::Double("jisx0213_2004_2")),
],
},
Iso2022Variant::Jp3 => Iso2022Config {
no_shift: true,
use_g2: false,
jisx0208_ext: true,
designations: &[
(b'O' | DBCS, Designation::Double("jisx0213_2000_1")),
JISX0208,
(b'P' | DBCS, Designation::Double("jisx0213_2000_2")),
],
},
Iso2022Variant::JpExt => Iso2022Config {
no_shift: true,
use_g2: false,
jisx0208_ext: true,
designations: &[JISX0208, JISX0212, JISX0201_R, JISX0201_K, JISX0208_O],
},
}
}
fn is_escape_end(byte: u8) -> bool {
byte.is_ascii_uppercase() || byte == b'@'
}
fn iso2022_escape(config: &Iso2022Config, data: &[u8]) -> Result<(usize, usize, u8), Step> {
let mut escape_length = 0usize;
let mut index = 1usize;
while index < 16 {
if index >= data.len() {
return Err(Step::Incomplete);
}
if is_escape_end(data[index]) {
escape_length = index + 1;
break;
} else if config.jisx0208_ext
&& index + 1 < data.len()
&& data[index] == b'&'
&& data[index + 1] == b'@'
{
index += 2;
}
index += 1;
}
let (charset, designation) = match escape_length {
0 => return Err(Step::Error(1)),
3 => {
if data[1] == b'$' {
(data[2] | DBCS, 0)
} else {
let designation = match data[1] {
b'(' => 0,
b')' => 1,
b'.' if config.use_g2 => 2,
_ => return Err(Step::Error(3)),
};
(data[2], designation)
}
}
4 => {
if data[1] != b'$' {
return Err(Step::Error(4));
}
let designation = match data[2] {
b'(' => 0,
b')' => 1,
_ => return Err(Step::Error(4)),
};
(data[3] | DBCS, designation)
}
6 => {
if config.jisx0208_ext && data[3] == 0x1B && data[4] == b'$' && data[5] == b'B' {
(b'B' | DBCS, 0)
} else {
return Err(Step::Error(6));
}
}
length => return Err(Step::Error(length)),
};
if charset != CHARSET_ASCII && !config.designations.iter().any(|(mark, _)| *mark == charset) {
return Err(Step::Error(escape_length));
}
Ok((escape_length, designation, charset))
}
fn iso8859_7_decode(byte: u8) -> Option<char> {
let c = u32::from(byte);
let value = if c < 0xA0 || (c < 0xC0 && (0x288f_3bc9u32 & (1u32 << (c - 0xA0))) != 0) {
c
} else if (0xB4..=0xFE).contains(&c)
&& (c >= 0xD4 || (0xbfff_fd77u32 & (1u32 << (c - 0xB4))) != 0)
{
0x02D0 + c
} else if c == 0xA1 {
0x2018
} else if c == 0xA2 {
0x2019
} else if c == 0xAF {
0x2015
} else {
return None;
};
char::from_u32(value)
}
fn single_shift(g2: u8, rest: &[u8], out: &mut String) -> Step {
if rest.len() < 3 {
return Step::Incomplete;
}
let value = rest[2];
let decoded = match g2 {
CHARSET_ISO8859_1 => (value < 0x80).then(|| char::from(value + 0x80)),
CHARSET_ISO8859_7 => iso8859_7_decode(value ^ 0x80),
CHARSET_ASCII => (value & 0x80 == 0).then_some(char::from(value)),
_ => None, };
match decoded {
Some(character) => {
out.push(character);
Step::Ok(3)
}
None => Step::Error(3),
}
}
fn designated(
config: &Iso2022Config,
charset: u8,
rest: &[u8],
out: &mut String,
) -> Result<Step, DecodeError> {
let tables = cjk();
let designation = config
.designations
.iter()
.find(|(mark, _)| *mark == charset)
.map_or(Designation::Dummy, |(_, designation)| *designation);
Ok(match designation {
Designation::Double(_) if rest.len() < 2 => Step::Incomplete,
Designation::Double(table) => match tables.iso2022[table].get(rest[0], rest[1]) {
Some(value) => {
push_value(out, value, &tables.pairs)?;
Step::Ok(2)
}
None => Step::Error(2),
},
Designation::Single(table) => match tables.iso2022[table].get(0, rest[0]) {
Some(value) => {
push_value(out, value, &tables.pairs)?;
Step::Ok(1)
}
None => Step::Error(1),
},
Designation::Dummy => Step::Error(1),
})
}
pub(super) fn decode_iso2022(
variant: Iso2022Variant,
data: &[u8],
errors: Errors,
out: &mut String,
) -> Result<(), DecodeError> {
const ESC: u8 = 0x1B;
const SO: u8 = 0x0E;
const SI: u8 = 0x0F;
const LF: u8 = 0x0A;
let config = iso2022_config(variant);
out.reserve(data.len());
let mut g = [CHARSET_ASCII; 4];
let mut shifted = false;
let mut escape_throughout = false;
let mut position = 0usize;
while position < data.len() {
let rest = &data[position..];
let byte = rest[0];
if escape_throughout {
out.push(char::from(byte)); position += 1;
if is_escape_end(byte) {
escape_throughout = false;
}
continue;
}
let bypass = |out: &mut String| {
out.push(char::from(byte));
Step::Ok(1)
};
let step = match byte {
ESC if rest.len() < 2 => Step::Incomplete,
ESC if matches!(rest[1], b'(' | b')' | b'$' | b'.' | b'&') => {
match iso2022_escape(&config, rest) {
Ok((length, designation, charset)) => {
g[designation] = charset;
Step::Ok(length)
}
Err(step) => step,
}
}
ESC if config.use_g2 && rest[1] == b'N' => single_shift(g[2], rest, out),
ESC => {
out.push(char::from(ESC));
escape_throughout = true;
Step::Ok(1)
}
SI | SO if config.no_shift => bypass(out),
SI => {
shifted = false;
Step::Ok(1)
}
SO => {
shifted = true;
Step::Ok(1)
}
LF => {
shifted = false;
out.push('\n');
Step::Ok(1)
}
_ if byte < 0x20 => bypass(out),
_ if byte >= 0x80 => Step::Error(1),
_ => {
let charset = if shifted { g[1] } else { g[0] };
if charset == CHARSET_ASCII {
bypass(out)
} else {
designated(&config, charset, rest, out)?
}
}
};
if !advance(step, errors, &mut position)? {
break;
}
}
Ok(())
}