blob: dcf9ec319609b6fa4f3f27831b9b69db5582b0e9 [file] [edit]
//! small utilities used in tests
use font_types::Scalar;
use std::collections::HashMap;
/// A convenience type for generating a buffer of big-endian bytes.
#[derive(Debug, Clone, Default)]
pub struct BeBuffer {
data: Vec<u8>,
tagged_locations: HashMap<String, usize>,
}
impl BeBuffer {
pub fn new() -> Self {
Default::default()
}
/// The current length of the buffer in bytes.
pub fn len(&self) -> usize {
self.data.len()
}
/// Returns `true` if the buffer contains zero bytes.
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
/// Return a reference to the contents of the buffer
pub fn as_slice(&self) -> &[u8] {
&self.data
}
/// Write any scalar to this buffer.
pub fn push(mut self, item: impl Scalar) -> Self {
self.data.extend(item.to_raw().as_ref());
self
}
pub fn push_with_tag(mut self, item: impl Scalar, tag: &str) -> Self {
self.tagged_locations
.insert(tag.to_string(), self.data.len());
self.data.extend(item.to_raw().as_ref());
self
}
/// Write multiple scalars into the buffer
pub fn extend<T: Scalar>(mut self, iter: impl IntoIterator<Item = T>) -> Self {
for item in iter {
self.data.extend(item.to_raw().as_ref());
}
self
}
pub fn offset_for(&self, tag: &str) -> usize {
// panic on unrecognized tags
self.tagged_locations.get(tag).copied().unwrap()
}
fn data_for(&mut self, tag: &str) -> &mut [u8] {
let offset = self.offset_for(tag);
&mut self.data[offset..]
}
pub fn write_at(&mut self, tag: &str, item: impl Scalar) {
let data = self.data_for(tag);
let raw = item.to_raw();
let new_data: &[u8] = raw.as_ref();
if data.len() < new_data.len() {
panic!("not enough room left in buffer for the requested write.");
}
for (left, right) in data.iter_mut().zip(new_data) {
*left = *right
}
}
pub fn data(&self) -> &[u8] {
&self.data
}
}
impl std::ops::Deref for BeBuffer {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.data
}
}
/// be_buffer_add!(buffer, value) - Add an item to a be_buffer.
///
/// Will call the appropriate insertion method on BeBuffer depending on the values format.
///
/// Values can be one of three types:
/// 1. Single numeric value, ie: 10u16
/// 2. Array of numeric values, ie: [1u8, 2, 3]
/// 3. Tagged value: {10u16, "tag_name"}. This will insert the numeric value
/// and attach the provide string tag to the insertion location in the buffer.
#[macro_export]
macro_rules! be_buffer_add {
($b:ident, $v:literal) => {
let $b = $b.push($v);
};
($b:ident, {$v:tt : $tag:literal}) => {
let $b = $b.push_with_tag($v, $tag);
};
($b:ident, [$($v:literal),+]) => {
let $b = $b.extend([$($v),*]);
};
($b:ident, $v:tt) => {
let $b = $b.push($v);
};
}
/// be_buffer!(val1, ..., valn) - Constructs a BeBuffer from the provided list of values:
///
/// Values can be one of three types:
/// 1. Single numeric value, ie: 10u16
/// 2. Array of numeric values, ie: [1u8, 2, 3]
/// 3. Tagged value: {10u16, "tag_name"}. This will insert the numeric value
/// and attach the provide string tag to the insertion location in the buffer.
#[macro_export]
macro_rules! be_buffer {
( $( $x:tt ),+ ) => {
{
let builder = BeBuffer::new();
$(
$crate::be_buffer_add!(builder, $x);
)*
builder
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn be_buffer_macro() {
let data: &[u8] = &be_buffer! {
1u8,
2u16,
3u32
};
assert_eq!([1, 0, 2, 0, 0, 0, 3], data);
}
#[test]
fn be_buffer_macro_array() {
let data: &[u8] = &be_buffer! {
1u8,
[2u8, 3, 4, 5, 6]
};
assert_eq!([1, 2, 3, 4, 5, 6], data);
}
#[test]
fn be_buffer_macro_tagged() {
let builder = be_buffer! {
1u8,
{2u16: "foo"},
{(u32::from(3u16)): "bar"}
};
let data: &[u8] = &builder;
assert_eq!([1, 0, 2, 0, 0, 0, 3], data);
assert_eq!(builder.offset_for("foo"), 1);
assert_eq!(builder.offset_for("bar"), 3);
}
}