Skip to main content

ferrijs_std/text/
text_encoder.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3use crate::utils::{bytes::get_lossy_string, result::ResultExt};
4use rquickjs::{
5    atom::PredefinedAtom, function::Opt, Ctx, Exception, Object, Result, TypedArray, Value,
6};
7
8#[derive(rquickjs::class::Trace, rquickjs::JsLifetime)]
9#[rquickjs::class]
10pub struct TextEncoder {}
11
12impl Default for TextEncoder {
13    fn default() -> Self {
14        Self::new()
15    }
16}
17
18#[rquickjs::methods(rename_all = "camelCase")]
19impl TextEncoder {
20    #[qjs(constructor)]
21    pub fn new() -> Self {
22        Self {}
23    }
24
25    #[qjs(get)]
26    fn encoding(&self) -> &str {
27        "utf-8"
28    }
29
30    #[qjs(prop, rename = PredefinedAtom::SymbolToStringTag, configurable)]
31    pub fn to_string_tag() -> &'static str {
32        stringify!(TextEncoder)
33    }
34
35    pub fn encode<'js>(&self, ctx: Ctx<'js>, string: Opt<Value<'js>>) -> Result<Value<'js>> {
36        if let Some(string) = string.0 {
37            if string.is_string() {
38                let s = get_lossy_string(string)?;
39                return TypedArray::new(ctx.clone(), s.as_bytes())
40                    .map(|m: TypedArray<'_, u8>| m.into_value());
41            } else if !string.is_undefined() {
42                return Err(Exception::throw_message(
43                    &ctx,
44                    "The \"string\" argument must be a string.",
45                ));
46            }
47        }
48
49        TypedArray::new(ctx.clone(), []).map(|m: TypedArray<'_, u8>| m.into_value())
50    }
51
52    pub fn encode_into<'js>(
53        &self,
54        ctx: Ctx<'js>,
55        src: Value<'js>,
56        dst: Value<'js>,
57    ) -> Result<Object<'js>> {
58        if !src.is_string() {
59            return Err(Exception::throw_type(&ctx, "src must be a string"));
60        }
61        let src = get_lossy_string(src)?;
62        let src = src.as_str();
63        if let Ok(typed_array) = TypedArray::<u8>::from_value(dst) {
64            let dst_length = typed_array.len();
65            let dst_offset: usize = typed_array.get("byteOffset")?;
66            let array_buffer = typed_array.arraybuffer()?;
67            let raw = array_buffer
68                .as_raw()
69                .ok_or("ArrayBuffer is detached")
70                .or_throw(&ctx)?;
71
72            let dst = unsafe {
73                std::slice::from_raw_parts_mut(raw.cast::<u8>().as_ptr().add(dst_offset), dst_length)
74            };
75
76            let mut written = 0;
77            let dst_len = dst.len();
78            for ch in src.chars() {
79                let len = ch.len_utf8();
80                if written + len > dst_len {
81                    break;
82                }
83                written += len;
84            }
85            dst[..written].copy_from_slice(&src.as_bytes()[..written]);
86            let read: usize = src[..written].chars().map(char::len_utf16).sum();
87
88            let obj = Object::new(ctx)?;
89            obj.set("read", read)?;
90            obj.set("written", written)?;
91            Ok(obj)
92        } else {
93            Err(Exception::throw_type(
94                &ctx,
95                "The \"dest\" argument must be an instance of Uint8Array.",
96            ))
97        }
98    }
99}