use crate::utils::{bytes::get_lossy_string, result::ResultExt};
use rquickjs::{
atom::PredefinedAtom, function::Opt, Ctx, Exception, Object, Result, TypedArray, Value,
};
#[derive(rquickjs::class::Trace, rquickjs::JsLifetime)]
#[rquickjs::class]
pub struct TextEncoder {}
impl Default for TextEncoder {
fn default() -> Self {
Self::new()
}
}
#[rquickjs::methods(rename_all = "camelCase")]
impl TextEncoder {
#[qjs(constructor)]
pub fn new() -> Self {
Self {}
}
#[qjs(get)]
fn encoding(&self) -> &str {
"utf-8"
}
#[qjs(prop, rename = PredefinedAtom::SymbolToStringTag, configurable)]
pub fn to_string_tag() -> &'static str {
stringify!(TextEncoder)
}
pub fn encode<'js>(&self, ctx: Ctx<'js>, string: Opt<Value<'js>>) -> Result<Value<'js>> {
if let Some(string) = string.0 {
if string.is_string() {
let s = get_lossy_string(string)?;
return TypedArray::new(ctx.clone(), s.as_bytes())
.map(|m: TypedArray<'_, u8>| m.into_value());
} else if !string.is_undefined() {
return Err(Exception::throw_message(
&ctx,
"The \"string\" argument must be a string.",
));
}
}
TypedArray::new(ctx.clone(), []).map(|m: TypedArray<'_, u8>| m.into_value())
}
pub fn encode_into<'js>(
&self,
ctx: Ctx<'js>,
src: Value<'js>,
dst: Value<'js>,
) -> Result<Object<'js>> {
if !src.is_string() {
return Err(Exception::throw_type(&ctx, "src must be a string"));
}
let src = get_lossy_string(src)?;
let src = src.as_str();
if let Ok(typed_array) = TypedArray::<u8>::from_value(dst) {
let dst_length = typed_array.len();
let dst_offset: usize = typed_array.get("byteOffset")?;
let array_buffer = typed_array.arraybuffer()?;
let raw = array_buffer
.as_raw()
.ok_or("ArrayBuffer is detached")
.or_throw(&ctx)?;
let dst = unsafe {
std::slice::from_raw_parts_mut(raw.ptr.as_ptr().add(dst_offset), dst_length)
};
let mut written = 0;
let dst_len = dst.len();
for ch in src.chars() {
let len = ch.len_utf8();
if written + len > dst_len {
break;
}
written += len;
}
dst[..written].copy_from_slice(&src.as_bytes()[..written]);
let read: usize = src[..written].chars().map(char::len_utf16).sum();
let obj = Object::new(ctx)?;
obj.set("read", read)?;
obj.set("written", written)?;
Ok(obj)
} else {
Err(Exception::throw_type(
&ctx,
"The \"dest\" argument must be an instance of Uint8Array.",
))
}
}
}