charcode 0.1.5

Character encoding conversion implementing the WHATWG Encoding Standard, with no external dependencies.
Documentation
//! EUC-KR, which in practice is the Unified Hangul Code (Windows codepage 949).

use crate::ascii::ascii_prefix_len_capped;
use crate::index;
use crate::result::{DecoderResult, EncoderResult};
use crate::sink::{ByteSink, DECODER_HEADROOM, ENCODER_HEADROOM};
use crate::tables::euc_kr::{
    EUC_KR_DECODE, EUC_KR_ENCODE_BUCKETS, EUC_KR_ENCODE_CODE_POINTS, EUC_KR_ENCODE_POINTERS,
};

/// `the code point for a byte pair` in KS X 1001, whose bytes here are the
/// 7-bit ones the ISO-2022 encodings shift in.
///
/// The standard's index EUC-KR is the Unified Hangul Code, which holds all
/// 8224 of KS X 1001 with none remapped, so the subset needs no table of its
/// own — only the byte range that marks where it ends.
#[cfg(any(feature = "iso-2022-kr", feature = "iso-2022-jp-2"))]
#[inline]
pub(crate) fn ksx1001_code_point(lead: u8, trail: u8) -> Option<u32> {
    if !(0x21..=0x7E).contains(&lead) || !(0x21..=0x7E).contains(&trail) {
        return None;
    }
    // The EUC-KR pointer for the same pair with the high bits set.
    index::code_point(
        &EUC_KR_DECODE,
        (usize::from(lead) - 0x01) * 190 + usize::from(trail) + 0x3F,
    )
}

/// The KS X 1001 byte pair for a code point, in the same 7-bit form.
#[cfg(any(feature = "iso-2022-kr", feature = "iso-2022-jp-2"))]
#[inline]
pub(crate) fn ksx1001_bytes(scalar: u32) -> Option<(u8, u8)> {
    let pointer = usize::from(index::pointer(
        &EUC_KR_ENCODE_CODE_POINTS,
        &EUC_KR_ENCODE_POINTERS,
        &EUC_KR_ENCODE_BUCKETS,
        scalar,
    )?);
    // The Unified Hangul Code extension area is not KS X 1001, so it has no
    // ISO-2022 form.  The check has to come first: below 0xA1 the shift back
    // to seven bits would go negative.
    let (lead, trail) = (pointer / 190 + 0x81, pointer % 190 + 0x41);
    if !(0xA1..=0xFE).contains(&lead) || !(0xA1..=0xFE).contains(&trail) {
        return None;
    }
    Some(((lead - 0x80) as u8, (trail - 0x80) as u8))
}

#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct EucKrDecoder {
    leading: u8,
}

impl EucKrDecoder {
    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.leading == 0 {
                let run = ascii_prefix_len_capped(&src[read..], sink.room());
                if run > 0 {
                    sink.write_slice(&src[read..read + run]);
                    read += run;
                    continue;
                }
            }

            let Some(byte) = src.get(read).copied() else {
                if !last {
                    return (DecoderResult::InputEmpty, read);
                }
                if self.leading == 0 {
                    return (DecoderResult::InputEmpty, read);
                }
                self.leading = 0;
                return (DecoderResult::Malformed(1), read);
            };
            read += 1;

            if self.leading != 0 {
                let leading = self.leading;
                self.leading = 0;
                let code_point = if (0x41..=0xFE).contains(&byte) {
                    let pointer = (usize::from(leading) - 0x81) * 190 + (usize::from(byte) - 0x41);
                    index::code_point(&EUC_KR_DECODE, pointer)
                } else {
                    None
                };
                if let Some(code_point) = code_point {
                    sink.write_code_point(code_point);
                    continue;
                }
                if byte.is_ascii() {
                    read -= 1;
                    return (DecoderResult::Malformed(1), read);
                }
                return (DecoderResult::Malformed(2), read);
            }

            if byte.is_ascii() {
                sink.write_byte(byte);
            } else if (0x81..=0xFE).contains(&byte) {
                self.leading = byte;
            } else {
                return (DecoderResult::Malformed(1), read);
            }
        }
    }
}

#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct EucKrEncoder;

impl EucKrEncoder {
    pub(crate) fn encode(&mut self, src: &str, sink: &mut ByteSink) -> (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 {
                return (EncoderResult::InputEmpty, read);
            };
            let run = ascii_prefix_len_capped(rest.as_bytes(), sink.room());
            if run > 0 {
                sink.write_slice(&rest.as_bytes()[..run]);
                read += run;
                continue;
            }
            read += c.len_utf8();

            let Some(pointer) = index::pointer(
                &EUC_KR_ENCODE_CODE_POINTS,
                &EUC_KR_ENCODE_POINTERS,
                &EUC_KR_ENCODE_BUCKETS,
                u32::from(c),
            ) else {
                return (EncoderResult::Unmappable(c), read);
            };
            let pointer = u32::from(pointer);
            sink.write_slice(&[(pointer / 190 + 0x81) as u8, (pointer % 190 + 0x41) as u8]);
        }
    }
}