mesh_loader/utils/
mod.rs

1pub(crate) mod bytes;
2#[cfg(any(feature = "collada", feature = "obj", feature = "stl"))]
3pub mod float;
4#[cfg(feature = "collada")]
5pub(crate) mod hex;
6#[cfg(any(feature = "collada", feature = "obj"))]
7pub mod int;
8#[cfg(feature = "collada")]
9pub(crate) mod xml;
10
11#[cfg(any(feature = "collada", feature = "obj"))]
12pub(crate) mod utf16 {
13    use std::{borrow::Cow, io};
14
15    use crate::error;
16
17    const UTF32BE_BOM: &[u8] = &[0xFF, 0xFE, 00, 00];
18    const UTF32LE_BOM: &[u8] = &[00, 00, 0xFE, 0xFF];
19    const UTF16BE_BOM: &[u8] = &[0xFE, 0xFF];
20    const UTF16LE_BOM: &[u8] = &[0xFF, 0xFE];
21    const UTF8_BOM: &[u8] = &[0xEF, 0xBB, 0xBF];
22
23    /// Converts bytes to a string. Converts to UTF-8 if bytes are UTF-16 and have BOM.
24    #[cfg(feature = "collada")]
25    pub(crate) fn decode_string(bytes: &[u8]) -> io::Result<Cow<'_, str>> {
26        if bytes.starts_with(UTF8_BOM) {
27            std::str::from_utf8(&bytes[UTF8_BOM.len()..])
28                .map(Cow::Borrowed)
29                .map_err(error::invalid_data)
30        } else if bytes.starts_with(UTF32BE_BOM) || bytes.starts_with(UTF32LE_BOM) {
31            return Err(error::invalid_data("utf-32 is not supported"));
32        } else if bytes.starts_with(UTF16BE_BOM) {
33            from_utf16be(&bytes[UTF16BE_BOM.len()..]).map(Into::into)
34        } else if bytes.starts_with(UTF16LE_BOM) {
35            from_utf16le(&bytes[UTF16BE_BOM.len()..]).map(Into::into)
36        } else {
37            // UTF-16/UTF-32 without BOM will get an error here.
38            std::str::from_utf8(bytes)
39                .map(Cow::Borrowed)
40                .map_err(error::invalid_data)
41        }
42    }
43
44    /// Converts to UTF-8 if bytes are UTF-16 and have BOM.
45    /// This does not handle UTF-16 without BOM or other UTF-8 incompatible encodings,
46    /// so the resulting bytes must not be trusted as a valid UTF-8.
47    #[cfg(feature = "obj")]
48    pub(crate) fn decode_bytes(bytes: &[u8]) -> io::Result<Cow<'_, [u8]>> {
49        if bytes.starts_with(UTF8_BOM) {
50            Ok(Cow::Borrowed(&bytes[UTF8_BOM.len()..]))
51        } else if bytes.starts_with(UTF32BE_BOM) || bytes.starts_with(UTF32LE_BOM) {
52            return Err(error::invalid_data("utf-32 is not supported"));
53        } else if bytes.starts_with(UTF16BE_BOM) {
54            from_utf16be(&bytes[UTF16BE_BOM.len()..])
55                .map(String::into_bytes)
56                .map(Into::into)
57        } else if bytes.starts_with(UTF16LE_BOM) {
58            from_utf16le(&bytes[UTF16BE_BOM.len()..])
59                .map(String::into_bytes)
60                .map(Into::into)
61        } else {
62            Ok(Cow::Borrowed(bytes))
63        }
64    }
65
66    #[cold]
67    #[inline(never)]
68    fn from_utf16be(bytes: &[u8]) -> io::Result<String> {
69        if bytes.len() % 2 != 0 {
70            return Err(error::invalid_data("invalid utf-16: lone surrogate found"));
71        }
72        char::decode_utf16(
73            bytes
74                .chunks_exact(2)
75                .map(|b| u16::from_be_bytes(b.try_into().unwrap())),
76        )
77        .collect::<Result<String, _>>()
78        .map_err(error::invalid_data)
79    }
80
81    #[cold]
82    #[inline(never)]
83    fn from_utf16le(bytes: &[u8]) -> io::Result<String> {
84        if bytes.len() % 2 != 0 {
85            return Err(error::invalid_data("invalid utf-16: lone surrogate found"));
86        }
87        char::decode_utf16(
88            bytes
89                .chunks_exact(2)
90                .map(|b| u16::from_le_bytes(b.try_into().unwrap())),
91        )
92        .collect::<Result<String, _>>()
93        .map_err(error::invalid_data)
94    }
95}