Repository: googlefonts/fontations
Pull request #966 (2024-06-24 20:29:15)

[read] Make ComputeSize fallible
This will ensure we do not panic on overflow when handling pathological inputs.

rsheeter: Testable?

Patch:
diff --git a/font-codegen/src/fields.rs b/font-codegen/src/fields.rs
index b97eb74a..0cef0a2c 100644
--- a/font-codegen/src/fields.rs
+++ b/font-codegen/src/fields.rs
@@ -1096,7 +1096,7 @@ impl Field {
             .map(FieldReadArgs::to_tokens_for_validation);
 
         if let FieldType::Struct { typ } = &self.typ {
-            return Some(quote!( <#typ as ComputeSize>::compute_size(&#read_args)));
+            return Some(quote!( <#typ as ComputeSize>::compute_size(&#read_args)? ));
         }
         if let FieldType::PendingResolution { .. } = &self.typ {
             panic!("Should have resolved {self:?}")
@@ -1123,7 +1123,7 @@ impl Field {
                     }
                     FieldType::ComputedArray(array) => {
                         let inner = array.raw_inner_type();
-                        quote!( <#inner as ComputeSize>::compute_size(&#read_args) )
+                        quote!( <#inner as ComputeSize>::compute_size(&#read_args)? )
                     }
                     _ => unreachable!("count not valid here"),
                 };
diff --git a/font-codegen/src/record.rs b/font-codegen/src/record.rs
index 33492e67..0a660420 100644
--- a/font-codegen/src/record.rs
+++ b/font-codegen/src/record.rs
@@ -90,9 +90,9 @@ fn generate_read_with_args(item: &Record) -> TokenStream {
         }
 
         impl ComputeSize for #name #anon_lifetime {
-            fn compute_size(args: &#args_type) -> usize {
+            fn compute_size(args: &#args_type) -> Result<usize, ReadError> {
                 let #destructure_pattern = *args;
-                #( #field_size_expr )+*
+               Ok( #( #field_size_expr )+* )
             }
         }
 
diff --git a/int-set/src/input_bit_stream.rs b/int-set/src/input_bit_stream.rs
new file mode 100644
index 00000000..e9d0c8a7
--- /dev/null
+++ b/int-set/src/input_bit_stream.rs
@@ -0,0 +1,137 @@
+//! Reads individual bits from a array of bytes.
+
+pub(crate) struct InputBitStream<'a> {
+    data: &'a [u8],
+    byte_index: usize,
+    bit_index: u8,
+}
+
+impl<'a> InputBitStream<'a> {
+    pub fn from(data: &'a [u8]) -> InputBitStream {
+        InputBitStream {
+            data,
+            byte_index: 0,
+            bit_index: 0,
+        }
+    }
+
+    /// Reads the next two bits and returns the corresponding branch factor.
+    ///
+    /// See: <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
+    /// Returns None if the stream does not have enough remaining bits.
+    pub fn read_branch_factor(&mut self) -> Option<u8> {
+        let bit_0 = self.read_bit()?;
+        let bit_1 = self.read_bit()?;
+
+        match (bit_1, bit_0) {
+            (false, false) => Some(2),
+            (false, true) => Some(4),
+            (true, false) => Some(8),
+            (true, true) => Some(32),
+        }
+    }
+
+    /// Reads the next 5 bits and returns the corresponding height value.
+    ///
+    /// See: <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
+    /// Returns None if the stream does not have enough remaining bits.
+    pub fn read_height(&mut self) -> Option<u8> {
+        let mut val = 0u8;
+        for index in 0..5 {
+            if self.read_bit()? {
+                val |= 1u8 << index;
+            }
+        }
+
+        Some(val)
+    }
+
+    /// Returns the value of the next bit, or None if there are no more bits left.
+    ///
+    /// Bits are read from least signifcant to most in the current byte.
+    pub fn read_bit(&mut self) -> Option<bool> {
+        let byte = self.data.get(self.byte_index)?;
+        let mask = 1u8 << self.bit_index;
+        let bit_value = byte & mask != 0;
+
+        self.move_to_next();
+
+        Some(bit_value)
+    }
+
+    /// Skips the next bit in the stream.
+    pub fn skip_bit(&mut self) {
+        self.move_to_next();
+    }
+
+    fn move_to_next(&mut self) {
+        self.bit_index = (self.bit_index + 1) % 8;
+        if self.bit_index == 0 {
+            self.byte_index += 1;
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn read_bit() {
+        let data = [0b00110010u8, 0b00000010u8];
+        let mut stream = InputBitStream::from(&data);
+
+        for bit in [0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0] {
+            assert_eq!(stream.read_bit(), Some(bit != 0));
+        }
+
+        assert_eq!(stream.read_bit(), None);
+    }
+
+    #[test]
+    fn read_branch_factor() {
+        let data = [0b11100100u8];
+        let mut stream = InputBitStream::from(&data);
+
+        assert_eq!(Some(2), stream.read_branch_factor());
+        assert_eq!(Some(4), stream.read_branch_factor());
+        assert_eq!(Some(8), stream.read_branch_factor());
+        assert_eq!(Some(32), stream.read_branch_factor());
+        assert_eq!(None, stream.read_branch_factor());
+    }
+
+    #[test]
+    #[allow(clippy::unusual_byte_groupings)]
+    fn read_height() {
+        let data = [0b0_00011_10u8, 0b0000_0001u8];
+        let mut stream = InputBitStream::from(&data);
+
+        stream.skip_bit();
+        stream.skip_bit();
+
+        assert_eq!(Some(3), stream.read_height());
+        assert_eq!(Some(2), stream.read_height());
+        assert_eq!(None, stream.read_height());
+    }
+
+    #[test]
+    fn skip() {
+        let data = [0b00110010u8];
+        let mut stream = InputBitStream::from(&data);
+
+        stream.skip_bit();
+        assert_eq!(stream.read_bit(), Some(true));
+        assert_eq!(stream.read_bit(), Some(false));
+        stream.skip_bit();
+        stream.skip_bit();
+        stream.skip_bit();
+        stream.skip_bit();
+        assert_eq!(stream.read_bit(), Some(false));
+        assert_eq!(stream.read_bit(), None);
+
+        // Skipping after the end has been reached.
+        stream.skip_bit();
+        stream.skip_bit();
+        assert_eq!(stream.read_bit(), None);
+    }
+}
diff --git a/int-set/src/lib.rs b/int-set/src/lib.rs
index 4a2ed7ad..28bad8d9 100644
--- a/int-set/src/lib.rs
+++ b/int-set/src/lib.rs
@@ -19,6 +19,9 @@
 
 mod bitpage;
 mod bitset;
+mod input_bit_stream;
+mod output_bit_stream;
+pub mod sparse_bit_set;
 
 use bitset::BitSet;
 use font_types::GlyphId;
@@ -591,6 +594,18 @@ mod test {
         }
     }
 
+    #[test]
+    fn from_sparse_set() {
+        let bytes = [0b00001101, 0b00000011, 0b00110001];
+
+        let set = IntSet::<u32>::from_sparse_bit_set(&bytes).unwrap();
+
+        let mut expected: IntSet<u32> = IntSet::<u32>::empty();
+        expected.insert_range(0..=17);
+
+        assert_eq!(set, expected);
+    }
+
     #[test]
     fn insert() {
         let mut empty = IntSet::<u32>::empty();
diff --git a/int-set/src/output_bit_stream.rs b/int-set/src/output_bit_stream.rs
new file mode 100644
index 00000000..3f79f154
--- /dev/null
+++ b/int-set/src/output_bit_stream.rs
@@ -0,0 +1,181 @@
+//! Writes individual bits to a vector of bytes.
+
+use crate::sparse_bit_set::BranchFactor;
+
+pub(crate) struct OutputBitStream {
+    data: Vec<u8>,
+    sub_index: u32,
+    branch_factor: BranchFactor,
+}
+
+impl OutputBitStream {
+    pub(crate) const MAX_HEIGHT: u8 = 32;
+
+    pub(crate) fn new(branch_factor: BranchFactor, height: u8) -> OutputBitStream {
+        let mut out = OutputBitStream {
+            data: vec![],
+            sub_index: 0,
+            branch_factor,
+        };
+        if height >= OutputBitStream::MAX_HEIGHT {
+            panic!("Height value exceeds 5 bits.");
+        }
+        out.write_header(height);
+        out
+    }
+
+    pub fn into_bytes(self) -> Vec<u8> {
+        self.data
+    }
+
+    /// Writes a single node worth of bits to the stream.
+    ///
+    /// branch_factor controls the node size.
+    pub fn write_node(&mut self, bits: u32) {
+        for byte_index in 0..self.branch_factor.bytes_per_node() {
+            if self.branch_factor.nodes_per_byte() == 1 || self.sub_index == 0 {
+                self.data.push(0);
+            }
+
+            let bits = (bits >> (byte_index * 8)) & self.branch_factor.byte_mask();
+            let bits = (bits << (self.sub_index * self.branch_factor.value())) as u8;
+            *self.data.last_mut().unwrap() |= bits;
+
+            if self.branch_factor.nodes_per_byte() > 1 {
+                self.sub_index = (self.sub_index + 1) % self.branch_factor.nodes_per_byte();
+            }
+        }
+    }
+
+    /// Writes the header byte for a sparse bit set.
+    ///
+    /// See: <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
+    fn write_header(&mut self, height: u8) {
+        let byte = (height & 0b00011111) << 2;
+        let byte = byte | self.branch_factor.bit_id();
+        self.data.push(byte);
+    }
+}
+
+impl BranchFactor {
+    fn nodes_per_byte(&self) -> u32 {
+        match self {
+            BranchFactor::Two => 4,
+            BranchFactor::Four => 2,
+            BranchFactor::Eight => 1,
+            BranchFactor::ThirtyTwo => 1,
+        }
+    }
+
+    fn bytes_per_node(&self) -> u32 {
+        match self {
+            BranchFactor::Two => 1,
+            BranchFactor::Four => 1,
+            BranchFactor::Eight => 1,
+            BranchFactor::ThirtyTwo => 4,
+        }
+    }
+
+    fn bit_id(&self) -> u8 {
+        match self {
+            BranchFactor::Two => 0b00,
+            BranchFactor::Four => 0b01,
+            BranchFactor::Eight => 0b10,
+            BranchFactor::ThirtyTwo => 0b11,
+        }
+    }
+
+    fn byte_mask(&self) -> u32 {
+        match self {
+            BranchFactor::Two => 0b00000011,
+            BranchFactor::Four => 0b00001111,
+            BranchFactor::Eight => 0b11111111,
+            BranchFactor::ThirtyTwo => 0b11111111,
+        }
+    }
+}
+
+#[cfg(test)]
+#[allow(clippy::unusual_byte_groupings)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn init() {
+        let os = OutputBitStream::new(BranchFactor::Two, 13);
+        assert_eq!(os.into_bytes(), vec![0b0_01101_00]);
+
+        let os = OutputBitStream::new(BranchFactor::Four, 23);
+        assert_eq!(os.into_bytes(), vec![0b0_10111_01]);
+
+        let os = OutputBitStream::new(BranchFactor::Eight, 1);
+        assert_eq!(os.into_bytes(), vec![0b0_00001_10]);
+
+        let os = OutputBitStream::new(BranchFactor::ThirtyTwo, 31);
+        assert_eq!(os.into_bytes(), vec![0b0_11111_11]);
+    }
+
+    #[test]
+    fn bf2() {
+        let mut os = OutputBitStream::new(BranchFactor::Two, 13);
+
+        os.write_node(0b10);
+        os.write_node(0b00);
+        os.write_node(0b11);
+        os.write_node(0b01);
+
+        os.write_node(0b01);
+        os.write_node(0b11);
+
+        assert_eq!(
+            os.into_bytes(),
+            vec![0b0_01101_00, 0b01_11_00_10, 0b00_00_11_01,]
+        );
+    }
+
+    #[test]
+    fn bf4() {
+        let mut os = OutputBitStream::new(BranchFactor::Four, 23);
+
+        os.write_node(0b0010);
+        os.write_node(0b0111);
+
+        os.write_node(0b1101);
+
+        assert_eq!(
+            os.into_bytes(),
+            vec![0b0_10111_01, 0b0111_0010, 0b0000_1101,]
+        );
+    }
+
+    #[test]
+    fn bf8() {
+        let mut os = OutputBitStream::new(BranchFactor::Eight, 1);
+
+        os.write_node(0b01110010);
+        os.write_node(0b00001101);
+
+        assert_eq!(os.into_bytes(), vec![0b0_00001_10, 0b01110010, 0b00001101,]);
+    }
+
+    #[test]
+    fn bf32() {
+        let mut os = OutputBitStream::new(BranchFactor::ThirtyTwo, 31);
+
+        os.write_node(0b10000000_00000000_00001101_01110010);
+
+        assert_eq!(
+            os.into_bytes(),
+            vec![0b0_11111_11, 0b01110010, 0b00001101, 0b00000000, 0b10000000]
+        );
+    }
+
+    #[test]
+    fn truncating() {
+        let mut os = OutputBitStream::new(BranchFactor::Four, 23);
+
+        os.write_node(0b11110010);
+
+        assert_eq!(os.into_bytes(), vec![0b0_10111_01, 0b0000_0010]);
+    }
+}
diff --git a/int-set/src/sparse_bit_set.rs b/int-set/src/sparse_bit_set.rs
new file mode 100644
index 00000000..b43efd90
--- /dev/null
+++ b/int-set/src/sparse_bit_set.rs
@@ -0,0 +1,444 @@
+//! Provides serialization of IntSet's to a highly compact bitset format as defined in the
+//! IFT specification:
+//!
+//! <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
+
+use std::collections::VecDeque;
+use std::error::Error;
+use std::fmt;
+
+use crate::input_bit_stream::InputBitStream;
+use crate::output_bit_stream::OutputBitStream;
+use crate::IntSet;
+
+#[derive(Debug)]
+pub struct DecodingError;
+
+impl Error for DecodingError {}
+
+impl fmt::Display for DecodingError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(
+            f,
+            "The input data stream was too short to be a valid sparse bit set."
+        )
+    }
+}
+
+#[derive(Copy, Clone)]
+pub(crate) enum BranchFactor {
+    Two,
+    Four,
+    Eight,
+    ThirtyTwo,
+}
+
+impl IntSet<u32> {
+    /// Populate this set with the values obtained from decoding the provided sparse bit set bytes.
+    ///
+    /// Sparse bit sets are a specialized, compact encoding of bit sets defined in the IFT specification:
+    /// <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
+    pub fn from_sparse_bit_set(data: &[u8]) -> Result<IntSet<u32>, DecodingError> {
+        // This is a direct port of the decoding algorithm from:
+        // <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
+        let mut bits = InputBitStream::from(data);
+
+        let Some(branch_factor) = bits.read_branch_factor() else {
+            return Err(DecodingError);
+        };
+
+        let Some(height) = bits.read_height() else {
+            return Err(DecodingError);
+        };
+
+        let mut out = IntSet::<u32>::empty();
+        if height == 0 {
+            return Ok(out);
+        }
+
+        // Bit 8 of header byte is ignored.
+        bits.skip_bit();
+
+        let mut queue = VecDeque::<NextNode>::new(); // TODO(garretrieger): estimate initial capacity?
+        queue.push_back(NextNode { start: 0, depth: 1 });
+
+        while let Some(next) = queue.pop_front() {
+            let mut has_a_one = false;
+            for index in 0..branch_factor as u32 {
+                let Some(bit) = bits.read_bit() else {
+                    return Err(DecodingError);
+                };
+
+                if !bit {
+                    continue;
+                }
+
+                // TODO(garretrieger): use two while loops (one for non-leaf and one for leaf nodes)
+                //                     to avoid having to branch on each iteration.
+                has_a_one = true;
+                if next.depth == height as u32 {
+                    // TODO(garretrieger): optimize insertion speed by using the bulk sorted insert
+                    // (rewrite this to be an iterator) or even directly writing groups of bits to the pages.
+                    out.insert(next.start + index);
+                } else {
+                    let exp = height as u32 - next.depth;
+                    queue.push_back(NextNode {
+                        start: next.start + index * (branch_factor as u32).pow(exp),
+                        depth: next.depth + 1,
+                    });
+                }
+            }
+
+            if !has_a_one {
+                // all bits were zeroes which is a special command to completely fill in
+                // all integers covered by this node.
+                let exp = (height as u32) - next.depth + 1;
+                out.insert_range(next.start..=next.start + (branch_factor as u32).pow(exp) - 1);
+            }
+        }
+
+        Ok(out)
+    }
+
+    /// Encodeg this set as a sparse bit set byte encoding.
+    ///
+    /// Sparse bit sets are a specialized, compact encoding of bit sets defined in the IFT specification:
+    /// <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
+    pub fn to_sparse_bit_set(&self) -> Vec<u8> {
+        // TODO(garretrieger): use the heuristic approach from the incxfer
+        // implementation to guess the optimal size. Building the set 4 times
+        // is costly.
+        let mut candidates: Vec<Vec<u8>> = vec![];
+
+        let Some(max_value) = self.last() else {
+            return OutputBitStream::new(BranchFactor::Two, 0).into_bytes();
+        };
+
+        if BranchFactor::Two.tree_height_for(max_value) < OutputBitStream::MAX_HEIGHT {
+            candidates.push(to_sparse_bit_set_with_bf::<2>(self));
+        }
+
+        if BranchFactor::Four.tree_height_for(max_value) < OutputBitStream::MAX_HEIGHT {
+            candidates.push(to_sparse_bit_set_with_bf::<4>(self));
+        }
+
+        if BranchFactor::Eight.tree_height_for(max_value) < OutputBitStream::MAX_HEIGHT {
+            candidates.push(to_sparse_bit_set_with_bf::<8>(self));
+        }
+
+        if BranchFactor::ThirtyTwo.tree_height_for(max_value) < OutputBitStream::MAX_HEIGHT {
+            candidates.push(to_sparse_bit_set_with_bf::<32>(self));
+        }
+
+        candidates.into_iter().min_by_key(|f| f.len()).unwrap()
+    }
+}
+
+fn to_sparse_bit_set_with_bf<const BF: u8>(set: &IntSet<u32>) -> Vec<u8> {
+    // TODO(garretrieger): implement detection of filled nodes (ie. zero nodes)
+    let branch_factor = BranchFactor::from_val(BF);
+    let Some(max_value) = set.last() else {
+        return OutputBitStream::new(branch_factor, 0).into_bytes();
+    };
+    let mut height = branch_factor.tree_height_for(max_value);
+    let mut os = OutputBitStream::new(branch_factor, height);
+    let mut nodes: Vec<Node> = vec![];
+
+    // We build the nodes that will comprise the bit stream in reverse order
+    // from the last value in the last layer up to the first layer. Then
+    // when generating the final stream the order is reversed.
+    // The reverse order construction is needed since nodes at the lower layer
+    // affect the values in the parent layers.
+    let mut indices = set.clone();
+    while height > 0 {
+        indices = create_layer(branch_factor, indices.iter(), &mut nodes);
+        height -= 1;
+    }
+
+    for node in nodes.iter().rev() {
+        os.write_node(node.bits);
+    }
+
+    os.into_bytes()
+}
+
+/// Compute the nodes for a layer of the sparse bit set.
+///
+/// Computes the nodes needed for the layer which contains the indices in
+/// 'iter'. The new nodes are appeded to 'nodes'. 'iter' must be sorted
+/// in ascending order.
+///
+/// Returns the set of indices for the layer above.
+fn create_layer<T: DoubleEndedIterator<Item = u32>>(
+    branch_factor: BranchFactor,
+    iter: T,
+    nodes: &mut Vec<Node>,
+) -> IntSet<u32> {
+    let mut next_indices = IntSet::<u32>::empty();
+
+    // The nodes array is produced in reverse order and then reversed before final output.
+    let mut current_node: Option<Node> = None;
+    for v in iter.rev() {
+        let parent_index = v / branch_factor.value();
+        let prev_parent_index = current_node
+            .as_ref()
+            .map_or(parent_index, |node| node.parent_index);
+        if prev_parent_index != parent_index {
+            nodes.push(current_node.take().unwrap());
+            next_indices.insert(prev_parent_index);
+        }
+
+        let current_node = current_node.get_or_insert(Node {
+            bits: 0,
+            parent_index,
+        });
+
+        current_node.bits |= 0b1 << (v % branch_factor.value());
+    }
+    if let Some(node) = current_node {
+        next_indices.insert(node.parent_index);
+        nodes.push(node);
+    }
+
+    next_indices
+}
+
+struct Node {
+    bits: u32,
+    parent_index: u32,
+}
+
+impl BranchFactor {
+    pub(crate) fn value(&self) -> u32 {
+        match self {
+            BranchFactor::Two => 2,
+            BranchFactor::Four => 4,
+            BranchFactor::Eight => 8,
+            BranchFactor::ThirtyTwo => 32,
+        }
+    }
+
+    fn tree_height_for(&self, max_value: u32) -> u8 {
+        // height H, can represent up to (BF^height) - 1
+        let mut height: u32 = 0;
+        let mut max_value = max_value;
+        loop {
+            height += 1;
+            max_value >>= self.node_size_log2();
+            if max_value == 0 {
+                break height as u8;
+            }
+        }
+    }
+
+    fn from_val(val: u8) -> BranchFactor {
+        match val {
+            2 => BranchFactor::Two,
+            4 => BranchFactor::Four,
+            8 => BranchFactor::Eight,
+            32 => BranchFactor::ThirtyTwo,
+            // This should never happen as this is only used internally.
+            _ => panic!("Invalid branch factor."),
+        }
+    }
+
+    fn node_size_log2(&self) -> u32 {
+        match self {
+            BranchFactor::Two => 1,
+            BranchFactor::Four => 2,
+            BranchFactor::Eight => 3,
+            BranchFactor::ThirtyTwo => 5,
+        }
+    }
+}
+
+struct NextNode {
+    start: u32,
+    depth: u32,
+}
+
+#[cfg(test)]
+#[allow(clippy::unusual_byte_groupings)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn spec_example_2() {
+        // Test of decoding the example 2 given in the specification.
+        // See: <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
+        let bytes = [
+            0b00001110, 0b00100001, 0b00010001, 0b00000001, 0b00000100, 0b00000010, 0b00001000,
+        ];
+
+        let set = IntSet::<u32>::from_sparse_bit_set(&bytes).unwrap();
+        let expected: IntSet<u32> = [2, 33, 323].iter().copied().collect();
+        assert_eq!(set, expected);
+    }
+
+    #[test]
+    fn spec_example_3() {
+        // Test of decoding the example 3 given in the specification.
+        // See: <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
+        let bytes = [0b00000000];
+
+        let set = IntSet::<u32>::from_sparse_bit_set(&bytes).unwrap();
+        let expected: IntSet<u32> = [].iter().copied().collect();
+        assert_eq!(set, expected);
+    }
+
+    #[test]
+    fn spec_example_4() {
+        // Test of decoding the example 4 given in the specification.
+        // See: <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
+        let bytes = [0b00001101, 0b00000011, 0b00110001];
+
+        let set = IntSet::<u32>::from_sparse_bit_set(&bytes).unwrap();
+
+        let mut expected: IntSet<u32> = IntSet::<u32>::empty();
+        expected.insert_range(0..=17);
+
+        assert_eq!(set, expected);
+    }
+
+    #[test]
+    fn invalid() {
+        // Spec example 2 with one byte missing.
+        let bytes = [
+            0b00001110, 0b00100001, 0b00010001, 0b00000001, 0b00000100, 0b00000010,
+        ];
+        assert!(IntSet::<u32>::from_sparse_bit_set(&bytes).is_err());
+    }
+
+    #[test]
+    fn test_tree_height_for() {
+        assert_eq!(BranchFactor::Two.tree_height_for(0), 1);
+        assert_eq!(BranchFactor::Two.tree_height_for(1), 1);
+        assert_eq!(BranchFactor::Two.tree_height_for(2), 2);
+        assert_eq!(BranchFactor::Two.tree_height_for(117), 7);
+
+        assert_eq!(BranchFactor::Four.tree_height_for(0), 1);
+        assert_eq!(BranchFactor::Four.tree_height_for(3), 1);
+        assert_eq!(BranchFactor::Four.tree_height_for(4), 2);
+        assert_eq!(BranchFactor::Four.tree_height_for(63), 3);
+        assert_eq!(BranchFactor::Four.tree_height_for(64), 4);
+
+        assert_eq!(BranchFactor::Eight.tree_height_for(0), 1);
+        assert_eq!(BranchFactor::Eight.tree_height_for(7), 1);
+        assert_eq!(BranchFactor::Eight.tree_height_for(8), 2);
+        assert_eq!(BranchFactor::Eight.tree_height_for(32767), 5);
+        assert_eq!(BranchFactor::Eight.tree_height_for(32768), 6);
+
+        assert_eq!(BranchFactor::ThirtyTwo.tree_height_for(0), 1);
+        assert_eq!(BranchFactor::ThirtyTwo.tree_height_for(31), 1);
+        assert_eq!(BranchFactor::ThirtyTwo.tree_height_for(32), 2);
+        assert_eq!(BranchFactor::ThirtyTwo.tree_height_for(1_048_575), 4);
+        assert_eq!(BranchFactor::ThirtyTwo.tree_height_for(1_048_576), 5);
+    }
+
+    #[test]
+    fn generate_spec_example_2() {
+        // Test of reproducing the encoding of example 2 given
+        // in the specification. See:
+        // <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
+
+        let actual_bytes = to_sparse_bit_set_with_bf::<8>(&[2, 33, 323].iter().copied().collect());
+        let expected_bytes = [
+            0b00001110, 0b00100001, 0b00010001, 0b00000001, 0b00000100, 0b00000010, 0b00001000,
+        ];
+
+        assert_eq!(actual_bytes, expected_bytes);
+    }
+
+    #[test]
+    fn generate_spec_example_3() {
+        // Test of reproducing the encoding of example 3 given
+        // in the specification. See:
+        // <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
+
+        let actual_bytes = to_sparse_bit_set_with_bf::<2>(&IntSet::<u32>::empty());
+        let expected_bytes = [0b00000000];
+
+        assert_eq!(actual_bytes, expected_bytes);
+    }
+
+    #[test]
+    fn encode_one_level() {
+        let actual_bytes = to_sparse_bit_set_with_bf::<8>(&[2, 6].iter().copied().collect());
+        let expected_bytes = [0b0_00001_10, 0b01000100];
+        assert_eq!(actual_bytes, expected_bytes);
+    }
+
+    #[test]
+    fn encode_bf32() {
+        let actual_bytes = to_sparse_bit_set_with_bf::<32>(&[2, 31, 323].iter().copied().collect());
+        let expected_bytes = [
+            0b0_00010_11,
+            // node 0
+            0b00000001,
+            0b00000100,
+            0b00000000,
+            0b00000000,
+            // node 1
+            0b00000100,
+            0b00000000,
+            0b00000000,
+            0b10000000,
+            // node 2
+            0b00001000,
+            0b00000000,
+            0b00000000,
+            0b00000000,
+        ];
+
+        assert_eq!(actual_bytes, expected_bytes);
+    }
+
+    #[test]
+    fn round_trip() {
+        let s1: IntSet<u32> = [11, 74, 9358].iter().copied().collect();
+        let mut s2: IntSet<u32> = s1.clone();
+        s2.insert_range(67..=412);
+
+        check_round_trip::<2>(&s1);
+        check_round_trip::<4>(&s1);
+        check_round_trip::<8>(&s1);
+        check_round_trip::<32>(&s1);
+
+        check_round_trip::<2>(&s2);
+        check_round_trip::<4>(&s2);
+        check_round_trip::<8>(&s2);
+        check_round_trip::<32>(&s2);
+    }
+
+    fn check_round_trip<const BF: u8>(s: &IntSet<u32>) {
+        let bytes = to_sparse_bit_set_with_bf::<BF>(s);
+        let s_prime = IntSet::<u32>::from_sparse_bit_set(&bytes).unwrap();
+        assert_eq!(*s, s_prime);
+    }
+
+    #[test]
+    fn find_smallest_bf() {
+        let s: IntSet<u32> = [11, 74, 9358].iter().copied().collect();
+        let bytes = s.to_sparse_bit_set();
+        // BF4
+        assert_eq!(vec![0b0_00111_01], bytes[0..1]);
+
+        let s: IntSet<u32> = [
+            16, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30,
+        ]
+        .iter()
+        .copied()
+        .collect();
+        let bytes = s.to_sparse_bit_set();
+        // BF32
+        assert_eq!(vec![0b0_00001_11], bytes[0..1]);
+    }
+
+    #[test]
+    fn encode_maxu32() {
+        let s: IntSet<u32> = [1, u32::MAX].iter().copied().collect();
+        let bytes = s.to_sparse_bit_set();
+        let s_prime = IntSet::<u32>::from_sparse_bit_set(&bytes);
+        assert_eq!(s, s_prime.unwrap());
+    }
+}
diff --git a/read-fonts/generated/generated_fvar.rs b/read-fonts/generated/generated_fvar.rs
index 3c26fd00..3668305c 100644
--- a/read-fonts/generated/generated_fvar.rs
+++ b/read-fonts/generated/generated_fvar.rs
@@ -178,7 +178,7 @@ impl<'a> FontReadWithArgs<'a> for AxisInstanceArrays<'a> {
         let axes_byte_len = axis_count as usize * VariationAxisRecord::RAW_BYTE_LEN;
         cursor.advance_by(axes_byte_len);
         let instances_byte_len = instance_count as usize
-            * <InstanceRecord as ComputeSize>::compute_size(&(axis_count, instance_size));
+            * <InstanceRecord as ComputeSize>::compute_size(&(axis_count, instance_size))?;
         cursor.advance_by(instances_byte_len);
         cursor.finish(AxisInstanceArraysMarker {
             axis_count,
diff --git a/read-fonts/generated/generated_gpos.rs b/read-fonts/generated/generated_gpos.rs
index 1d8311b9..6da784c1 100644
--- a/read-fonts/generated/generated_gpos.rs
+++ b/read-fonts/generated/generated_gpos.rs
@@ -1197,7 +1197,7 @@ impl<'a> FontRead<'a> for SinglePosFormat1<'a> {
         cursor.advance::<u16>();
         cursor.advance::<Offset16>();
         let value_format: ValueFormat = cursor.read()?;
-        let value_record_byte_len = <ValueRecord as ComputeSize>::compute_size(&value_format);
+        let value_record_byte_len = <ValueRecord as ComputeSize>::compute_size(&value_format)?;
         cursor.advance_by(value_record_byte_len);
         cursor.finish(SinglePosFormat1Marker {
             value_record_byte_len,
@@ -1314,7 +1314,7 @@ impl<'a> FontRead<'a> for SinglePosFormat2<'a> {
         let value_format: ValueFormat = cursor.read()?;
         let value_count: u16 = cursor.read()?;
         let value_records_byte_len =
-            value_count as usize * <ValueRecord as ComputeSize>::compute_size(&value_format);
+            value_count as usize * <ValueRecord as ComputeSize>::compute_size(&value_format)?;
         cursor.advance_by(value_records_byte_len);
         cursor.finish(SinglePosFormat2Marker {
             value_records_byte_len,
@@ -1668,7 +1668,7 @@ impl<'a> FontReadWithArgs<'a> for PairSet<'a> {
         let mut cursor = data.cursor();
         let pair_value_count: u16 = cursor.read()?;
         let pair_value_records_byte_len = pair_value_count as usize
-            * <PairValueRecord as ComputeSize>::compute_size(&(value_format1, value_format2));
+            * <PairValueRecord as ComputeSize>::compute_size(&(value_format1, value_format2))?;
         cursor.advance_by(pair_value_records_byte_len);
         cursor.finish(PairSetMarker {
             value_format1,
@@ -1784,11 +1784,11 @@ impl ReadArgs for PairValueRecord {
 }
 
 impl ComputeSize for PairValueRecord {
-    fn compute_size(args: &(ValueFormat, ValueFormat)) -> usize {
+    fn compute_size(args: &(ValueFormat, ValueFormat)) -> Result<usize, ReadError> {
         let (value_format1, value_format2) = *args;
-        GlyphId::RAW_BYTE_LEN
-            + <ValueRecord as ComputeSize>::compute_size(&value_format1)
-            + <ValueRecord as ComputeSize>::compute_size(&value_format2)
+        Ok(GlyphId::RAW_BYTE_LEN
+            + <ValueRecord as ComputeSize>::compute_size(&value_format1)?
+            + <ValueRecord as ComputeSize>::compute_size(&value_format2)?)
     }
 }
 
@@ -1910,7 +1910,7 @@ impl<'a> FontRead<'a> for PairPosFormat2<'a> {
                 class2_count,
                 value_format1,
                 value_format2,
-            ));
+            ))?;
         cursor.advance_by(class1_records_byte_len);
         cursor.finish(PairPosFormat2Marker {
             class1_records_byte_len,
@@ -2071,10 +2071,10 @@ impl ReadArgs for Class1Record<'_> {
 }
 
 impl ComputeSize for Class1Record<'_> {
-    fn compute_size(args: &(u16, ValueFormat, ValueFormat)) -> usize {
+    fn compute_size(args: &(u16, ValueFormat, ValueFormat)) -> Result<usize, ReadError> {
         let (class2_count, value_format1, value_format2) = *args;
-        class2_count as usize
-            * <Class2Record as ComputeSize>::compute_size(&(value_format1, value_format2))
+        Ok(class2_count as usize
+            * <Class2Record as ComputeSize>::compute_size(&(value_format1, value_format2))?)
     }
 }
 
@@ -2155,10 +2155,10 @@ impl ReadArgs for Class2Record {
 }
 
 impl ComputeSize for Class2Record {
-    fn compute_size(args: &(ValueFormat, ValueFormat)) -> usize {
+    fn compute_size(args: &(ValueFormat, ValueFormat)) -> Result<usize, ReadError> {
         let (value_format1, value_format2) = *args;
-        <ValueRecord as ComputeSize>::compute_size(&value_format1)
-            + <ValueRecord as ComputeSize>::compute_size(&value_format2)
+        Ok(<ValueRecord as ComputeSize>::compute_size(&value_format1)?
+            + <ValueRecord as ComputeSize>::compute_size(&value_format2)?)
     }
 }
 
@@ -2585,7 +2585,7 @@ impl<'a> FontReadWithArgs<'a> for BaseArray<'a> {
         let mut cursor = data.cursor();
         let base_count: u16 = cursor.read()?;
         let base_records_byte_len =
-            base_count as usize * <BaseRecord as ComputeSize>::compute_size(&mark_class_count);
+            base_count as usize * <BaseRecord as ComputeSize>::compute_size(&mark_class_count)?;
         cursor.advance_by(base_records_byte_len);
         cursor.finish(BaseArrayMarker {
             mark_class_count,
@@ -2693,9 +2693,9 @@ impl ReadArgs for BaseRecord<'_> {
 }
 
 impl ComputeSize for BaseRecord<'_> {
-    fn compute_size(args: &u16) -> usize {
+    fn compute_size(args: &u16) -> Result<usize, ReadError> {
         let mark_class_count = *args;
-        mark_class_count as usize * Offset16::RAW_BYTE_LEN
+        Ok(mark_class_count as usize * Offset16::RAW_BYTE_LEN)
     }
 }
 
@@ -3045,7 +3045,7 @@ impl<'a> FontReadWithArgs<'a> for LigatureAttach<'a> {
         let mut cursor = data.cursor();
         let component_count: u16 = cursor.read()?;
         let component_records_byte_len = component_count as usize
-            * <ComponentRecord as ComputeSize>::compute_size(&mark_class_count);
+            * <ComponentRecord as ComputeSize>::compute_size(&mark_class_count)?;
         cursor.advance_by(component_records_byte_len);
         cursor.finish(LigatureAttachMarker {
             mark_class_count,
@@ -3153,9 +3153,9 @@ impl ReadArgs for ComponentRecord<'_> {
 }
 
 impl ComputeSize for ComponentRecord<'_> {
-    fn compute_size(args: &u16) -> usize {
+    fn compute_size(args: &u16) -> Result<usize, ReadError> {
         let mark_class_count = *args;
-        mark_class_count as usize * Offset16::RAW_BYTE_LEN
+        Ok(mark_class_count as usize * Offset16::RAW_BYTE_LEN)
     }
 }
 
@@ -3391,7 +3391,7 @@ impl<'a> FontReadWithArgs<'a> for Mark2Array<'a> {
         let mut cursor = data.cursor();
         let mark2_count: u16 = cursor.read()?;
         let mark2_records_byte_len =
-            mark2_count as usize * <Mark2Record as ComputeSize>::compute_size(&mark_class_count);
+            mark2_count as usize * <Mark2Record as ComputeSize>::compute_size(&mark_class_count)?;
         cursor.advance_by(mark2_records_byte_len);
         cursor.finish(Mark2ArrayMarker {
             mark_class_count,
@@ -3499,9 +3499,9 @@ impl ReadArgs for Mark2Record<'_> {
 }
 
 impl ComputeSize for Mark2Record<'_> {
-    fn compute_size(args: &u16) -> usize {
+    fn compute_size(args: &u16) -> Result<usize, ReadError> {
         let mark_class_count = *args;
-        mark_class_count as usize * Offset16::RAW_BYTE_LEN
+        Ok(mark_class_count as usize * Offset16::RAW_BYTE_LEN)
     }
 }
 
diff --git a/read-fonts/generated/generated_gvar.rs b/read-fonts/generated/generated_gvar.rs
index d0389f24..5b855304 100644
--- a/read-fonts/generated/generated_gvar.rs
+++ b/read-fonts/generated/generated_gvar.rs
@@ -63,7 +63,7 @@ impl<'a> FontRead<'a> for Gvar<'a> {
         let flags: GvarFlags = cursor.read()?;
         cursor.advance::<u32>();
         let glyph_variation_data_offsets_byte_len =
-            transforms::add(glyph_count, 1_usize) * <U16Or32 as ComputeSize>::compute_size(&flags);
+            transforms::add(glyph_count, 1_usize) * <U16Or32 as ComputeSize>::compute_size(&flags)?;
         cursor.advance_by(glyph_variation_data_offsets_byte_len);
         cursor.finish(GvarMarker {
             glyph_variation_data_offsets_byte_len,
@@ -501,7 +501,7 @@ impl<'a> FontReadWithArgs<'a> for SharedTuples<'a> {
         let (shared_tuple_count, axis_count) = *args;
         let mut cursor = data.cursor();
         let tuples_byte_len =
-            shared_tuple_count as usize * <Tuple as ComputeSize>::compute_size(&axis_count);
+            shared_tuple_count as usize * <Tuple as ComputeSize>::compute_size(&axis_count)?;
         cursor.advance_by(tuples_byte_len);
         cursor.finish(SharedTuplesMarker {
             axis_count,
diff --git a/read-fonts/generated/generated_variations.rs b/read-fonts/generated/generated_variations.rs
index 1d6900fa..239f1ab8 100644
--- a/read-fonts/generated/generated_variations.rs
+++ b/read-fonts/generated/generated_variations.rs
@@ -147,9 +147,9 @@ impl ReadArgs for Tuple<'_> {
 }
 
 impl ComputeSize for Tuple<'_> {
-    fn compute_size(args: &u16) -> usize {
+    fn compute_size(args: &u16) -> Result<usize, ReadError> {
         let axis_count = *args;
-        axis_count as usize * F2Dot14::RAW_BYTE_LEN
+        Ok(axis_count as usize * F2Dot14::RAW_BYTE_LEN)
     }
 }
 
@@ -789,7 +789,7 @@ impl<'a> FontRead<'a> for VariationRegionList<'a> {
         let axis_count: u16 = cursor.read()?;
         let region_count: u16 = cursor.read()?;
         let variation_regions_byte_len =
-            region_count as usize * <VariationRegion as ComputeSize>::compute_size(&axis_count);
+            region_count as usize * <VariationRegion as ComputeSize>::compute_size(&axis_count)?;
         cursor.advance_by(variation_regions_byte_len);
         cursor.finish(VariationRegionListMarker {
             variation_regions_byte_len,
@@ -872,9 +872,9 @@ impl ReadArgs for VariationRegion<'_> {
 }
 
 impl ComputeSize for VariationRegion<'_> {
-    fn compute_size(args: &u16) -> usize {
+    fn compute_size(args: &u16) -> Result<usize, ReadError> {
         let axis_count = *args;
-        axis_count as usize * RegionAxisCoordinates::RAW_BYTE_LEN
+        Ok(axis_count as usize * RegionAxisCoordinates::RAW_BYTE_LEN)
     }
 }
 
diff --git a/read-fonts/src/array.rs b/read-fonts/src/array.rs
index 9709b252..5d3246d5 100644
--- a/read-fonts/src/array.rs
+++ b/read-fonts/src/array.rs
@@ -24,15 +24,15 @@ pub struct ComputedArray<'a, T: ReadArgs> {
 }
 
 impl<'a, T: ComputeSize> ComputedArray<'a, T> {
-    pub fn new(data: FontData<'a>, args: T::Args) -> Self {
-        let item_len = T::compute_size(&args);
+    pub fn new(data: FontData<'a>, args: T::Args) -> Result<Self, ReadError> {
+        let item_len = T::compute_size(&args)?;
         let len = data.len().checked_div(item_len).unwrap_or(0);
-        ComputedArray {
+        Ok(ComputedArray {
             item_len,
             len,
             data,
             args,
-        }
+        })
     }
 
     /// The number of items in the array
@@ -55,7 +55,7 @@ where
     T::Args: Copy,
 {
     fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
-        Ok(Self::new(data, *args))
+        Self::new(data, *args)
     }
 }
 
diff --git a/read-fonts/src/font_data.rs b/read-fonts/src/font_data.rs
index 1a27bd17..2f760b17 100644
--- a/read-fonts/src/font_data.rs
+++ b/read-fonts/src/font_data.rs
@@ -216,7 +216,7 @@ impl<'a> Cursor<'a> {
     where
         T: FontReadWithArgs<'a> + ComputeSize,
     {
-        let len = T::compute_size(args);
+        let len = T::compute_size(args)?;
         let temp = self.data.read_with_args(self.pos..self.pos + len, args);
         self.pos += len;
         temp
@@ -231,7 +231,7 @@ impl<'a> Cursor<'a> {
     where
         T: FontReadWithArgs<'a> + ComputeSize,
     {
-        let len = len * T::compute_size(args);
+        let len = len * T::compute_size(args)?;
         let temp = self.data.read_with_args(self.pos..self.pos + len, args);
         self.pos += len;
         temp
diff --git a/read-fonts/src/read.rs b/read-fonts/src/read.rs
index 7249fb86..c82926f2 100644
--- a/read-fonts/src/read.rs
+++ b/read-fonts/src/read.rs
@@ -71,7 +71,7 @@ pub trait Format<T> {
 /// for types which store their size inline, see [`VarSize`].
 pub trait ComputeSize: ReadArgs {
     /// Compute the number of bytes required to represent this type.
-    fn compute_size(args: &Self::Args) -> usize;
+    fn compute_size(args: &Self::Args) -> Result<usize, ReadError>;
 }
 
 /// A trait for types that have variable length.
diff --git a/read-fonts/src/tables/fvar.rs b/read-fonts/src/tables/fvar.rs
index 5fef988c..c799baa9 100644
--- a/read-fonts/src/tables/fvar.rs
+++ b/read-fonts/src/tables/fvar.rs
@@ -29,8 +29,12 @@ impl VariationAxisRecord {
         let max_value = self.max_value().max(min_value);
         value = value.clamp(min_value, max_value);
         value = match value.cmp(&default_value) {
-            Less => -((default_value - value) / (default_value - min_value)),
-            Greater => (value - default_value) / (max_value - default_value),
+            Less => {
+                -((default_value.saturating_sub(value)) / (default_value.saturating_sub(min_value)))
+            }
+            Greater => {
+                (value.saturating_sub(default_value)) / (max_value.saturating_sub(default_value))
+            }
             Equal => Fixed::ZERO,
         };
         value.clamp(-Fixed::ONE, Fixed::ONE)
@@ -92,4 +96,25 @@ mod tests {
             );
         }
     }
+
+    #[test]
+    fn normalize_overflow() {
+        // From: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=69787
+        // & https://oss-fuzz.com/testcase?key=6159008335986688
+        // fvar entry triggering overflow:
+        // min: -26335.87451171875 def 8224.12548828125 max 8224.12548828125
+        let test_case = &[
+            79, 84, 84, 79, 0, 1, 32, 32, 255, 32, 32, 32, 102, 118, 97, 114, 32, 32, 32, 32, 0, 0,
+            0, 28, 0, 0, 0, 41, 32, 0, 0, 0, 0, 1, 32, 32, 0, 2, 32, 32, 32, 32, 0, 0, 32, 32, 32,
+            32, 32, 0, 0, 0, 0, 153, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+        ];
+        let font = FontRef::new(test_case).unwrap();
+        let fvar = font.fvar().unwrap();
+        let axis = fvar.axes().unwrap()[1];
+        // Should not panic with "attempt to subtract with overflow".
+        assert_eq!(
+            axis.normalize(Fixed::from_f64(0.0)),
+            Fixed::from_f64(-0.2509765625)
+        );
+    }
 }
diff --git a/read-fonts/src/tables/gvar.rs b/read-fonts/src/tables/gvar.rs
index 140be85e..7a651679 100644
--- a/read-fonts/src/tables/gvar.rs
+++ b/read-fonts/src/tables/gvar.rs
@@ -22,12 +22,12 @@ impl ReadArgs for U16Or32 {
 }
 
 impl ComputeSize for U16Or32 {
-    fn compute_size(args: &GvarFlags) -> usize {
-        if args.contains(GvarFlags::LONG_OFFSETS) {
+    fn compute_size(args: &GvarFlags) -> Result<usize, ReadError> {
+        Ok(if args.contains(GvarFlags::LONG_OFFSETS) {
             4
         } else {
             2
-        }
+        })
     }
 }
 
diff --git a/read-fonts/src/tables/instance_record.rs b/read-fonts/src/tables/instance_record.rs
index 24f10f8c..047c6c44 100644
--- a/read-fonts/src/tables/instance_record.rs
+++ b/read-fonts/src/tables/instance_record.rs
@@ -64,8 +64,8 @@ impl<'a> FontReadWithArgs<'a> for InstanceRecord<'a> {
 
 impl ComputeSize for InstanceRecord<'_> {
     #[inline]
-    fn compute_size(args: &(u16, u16)) -> usize {
-        args.1 as usize
+    fn compute_size(args: &(u16, u16)) -> Result<usize, ReadError> {
+        Ok(args.1 as usize)
     }
 }
 
diff --git a/read-fonts/src/tables/value_record.rs b/read-fonts/src/tables/value_record.rs
index caa208ca..4bcaf6aa 100644
--- a/read-fonts/src/tables/value_record.rs
+++ b/read-fonts/src/tables/value_record.rs
@@ -172,8 +172,8 @@ impl std::fmt::Debug for ValueRecord {
 
 impl ComputeSize for ValueRecord {
     #[inline]
-    fn compute_size(args: &ValueFormat) -> usize {
-        args.record_byte_len()
+    fn compute_size(args: &ValueFormat) -> Result<usize, ReadError> {
+        Ok(args.record_byte_len())
     }
 }
 


Test patch:
diff --git a/read-fonts/generated/generated_test_records.rs b/read-fonts/generated/generated_test_records.rs
index 7747304e..ea4e8df1 100644
--- a/read-fonts/generated/generated_test_records.rs
+++ b/read-fonts/generated/generated_test_records.rs
@@ -44,7 +44,7 @@ impl<'a> FontRead<'a> for BasicTable<'a> {
         let arrays_inner_count: u16 = cursor.read()?;
         let array_records_count: u32 = cursor.read()?;
         let array_records_byte_len = array_records_count as usize
-            * <ContainsArrays as ComputeSize>::compute_size(&arrays_inner_count);
+            * <ContainsArrays as ComputeSize>::compute_size(&arrays_inner_count)?;
         cursor.advance_by(array_records_byte_len);
         cursor.finish(BasicTableMarker {
             simple_records_byte_len,
@@ -183,9 +183,12 @@ impl ReadArgs for ContainsArrays<'_> {
 }
 
 impl ComputeSize for ContainsArrays<'_> {
-    fn compute_size(args: &u16) -> usize {
+    fn compute_size(args: &u16) -> Result<usize, ReadError> {
         let array_len = *args;
-        array_len as usize * u16::RAW_BYTE_LEN + array_len as usize * SimpleRecord::RAW_BYTE_LEN
+        Ok(
+            array_len as usize * u16::RAW_BYTE_LEN
+                + array_len as usize * SimpleRecord::RAW_BYTE_LEN,
+        )
     }
 }