diff --git a/crates/typst/src/foundations/float.rs b/crates/typst/src/foundations/float.rs index fd094950b..ff3e62a50 100644 --- a/crates/typst/src/foundations/float.rs +++ b/crates/typst/src/foundations/float.rs @@ -2,7 +2,10 @@ use std::num::ParseFloatError; use ecow::{eco_format, EcoString}; -use crate::foundations::{cast, func, repr, scope, ty, Repr, Str}; +use crate::diag::StrResult; +use crate::foundations::{ + bail, cast, func, repr, scope, ty, Bytes, Endianness, Repr, Str, +}; use crate::layout::Ratio; /// A floating-point number. @@ -106,6 +109,58 @@ impl f64 { pub fn signum(self) -> f64 { f64::signum(self) } + + /// Converts bytes to a float. + /// + /// ```example + /// #float.from-bytes(bytes((0, 0, 0, 0, 0, 0, 240, 63))) \ + /// #float.from-bytes(bytes((63, 240, 0, 0, 0, 0, 0, 0)), endian: "big") + /// ``` + #[func] + pub fn from_bytes( + /// The bytes that should be converted to a float. + /// + /// Must be of length exactly 8 so that the result fits into a 64-bit + /// float. + bytes: Bytes, + /// The endianness of the conversion. + #[named] + #[default(Endianness::Little)] + endian: Endianness, + ) -> StrResult { + // Convert slice to an array of length 8. + let buf: [u8; 8] = match bytes.as_ref().try_into() { + Ok(buffer) => buffer, + Err(_) => bail!("bytes must have a length of exactly 8"), + }; + + Ok(match endian { + Endianness::Little => f64::from_le_bytes(buf), + Endianness::Big => f64::from_be_bytes(buf), + }) + } + + /// Converts a float to bytes. + /// + /// ```example + /// #array(1.0.to-bytes(endian: "big")) \ + /// #array(1.0.to-bytes()) + /// ``` + #[func] + pub fn to_bytes( + self, + /// The endianness of the conversion. + #[named] + #[default(Endianness::Little)] + endian: Endianness, + ) -> Bytes { + match endian { + Endianness::Little => self.to_le_bytes(), + Endianness::Big => self.to_be_bytes(), + } + .as_slice() + .into() + } } impl Repr for f64 { diff --git a/crates/typst/src/foundations/int.rs b/crates/typst/src/foundations/int.rs index 4c1335fc9..79a8a86f9 100644 --- a/crates/typst/src/foundations/int.rs +++ b/crates/typst/src/foundations/int.rs @@ -341,12 +341,15 @@ impl Repr for i64 { } } -/// Represents the byte order used for converting integers to bytes and vice versa. +/// Represents the byte order used for converting integers and floats to bytes +/// and vice versa. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] pub enum Endianness { - /// Big-endian byte order: the highest-value byte is at the beginning of the bytes. + /// Big-endian byte order: The highest-value byte is at the beginning of the + /// bytes. Big, - /// Little-endian byte order: the lowest-value byte is at the beginning of the bytes. + /// Little-endian byte order: The lowest-value byte is at the beginning of + /// the bytes. Little, } diff --git a/tests/suite/foundations/float.typ b/tests/suite/foundations/float.typ index 01769260e..67d4acbf7 100644 --- a/tests/suite/foundations/float.typ +++ b/tests/suite/foundations/float.typ @@ -42,6 +42,17 @@ #test(float(-calc.inf).signum(), -1.0) #test(float(float.nan).signum().is-nan(), true) +--- float-from-and-to-bytes --- +// Test float `from-bytes()` and `to-bytes()`. +#test(float.from-bytes(bytes((0, 0, 0, 0, 0, 0, 240, 63))), 1.0) +#test(float.from-bytes(bytes((63, 240, 0, 0, 0, 0, 0, 0)), endian: "big"), 1.0) +#test(1.0.to-bytes(), bytes((0, 0, 0, 0, 0, 0, 240, 63))) +#test(1.0.to-bytes(endian: "big"), bytes((63, 240, 0, 0, 0, 0, 0, 0))) + +--- float-from-bytes-bad-length --- +// Error: 2-54 bytes must have a length of exactly 8 +#float.from-bytes(bytes((0, 0, 0, 0, 0, 0, 0, 1, 0))) + --- float-repr --- // Test the `repr` function with floats. #repr(12.0) \