arsc/writer/
components_sizing.rs

1use crate::components::{
2    Arsc, Config, Header, Package, ResourceEntry, ResourceValue, Spec, Specs, StringPool, Type,
3    Value,
4};
5use crate::{Resources, Style, StyleSpan};
6
7/// A trait for objects that have constant sizes
8/// when being written out in arsc format
9pub(in crate::writer) trait ConstByteSizing {
10    /// The number of bytes required to write out this type
11    const SIZE: usize;
12}
13
14/// A trait for objects whose content affects its size
15/// when being written out in arsc format
16pub(in crate::writer) trait ByteSizing {
17    /// The number of bytes required to write out this type
18    fn size(&self) -> usize;
19}
20
21impl<T: ConstByteSizing> ByteSizing for T {
22    fn size(&self) -> usize {
23        T::SIZE
24    }
25}
26
27impl ConstByteSizing for Value {
28    const SIZE: usize = 2 + 1 + 1 + 4; // size + zero + type + data_index
29}
30
31impl ByteSizing for ResourceValue {
32    fn size(&self) -> usize {
33        match self {
34            ResourceValue::Plain(_) => Value::SIZE,
35            ResourceValue::Bag { parent: _, values } => {
36                4 + 4// parent + count
37                    + values.len() * (4 + Value::SIZE)
38            }
39        }
40    }
41}
42
43impl ByteSizing for ResourceEntry {
44    fn size(&self) -> usize {
45        2 + 2 + 4 + self.value.size() // _size + name_index + flags + name_index + value. `spec_id` is not read in
46    }
47}
48
49impl ByteSizing for Config {
50    fn size(&self) -> usize {
51        Header::SIZE + 1 + 1 + 2 + 4 + 4 // type_id + res0 + res1 + entry_count + _entry_start
52            + self.id.len()// config_id
53            + padding(self.id.len())// config_id_padding
54            + self.resources.size()
55    }
56}
57
58impl ByteSizing for Resources {
59    fn size(&self) -> usize {
60        self.entry_count() * 4 + self.resources.iter().map(ByteSizing::size).sum::<usize>()
61    }
62}
63
64impl ConstByteSizing for Spec {
65    const SIZE: usize = 4; // flags. name_index is handled in config part
66}
67
68impl ByteSizing for Specs {
69    fn size(&self) -> usize {
70        // parse_spec: header + type_id + _res0 + _res1 + entry_count + sizeOf(specs)
71        Header::SIZE + 1 + 1 + 2 + 4 + self.specs.len() * Spec::SIZE
72    }
73}
74
75impl ByteSizing for Type {
76    fn size(&self) -> usize {
77        let parse_spec = self.specs.as_ref().map(ByteSizing::size).unwrap_or(0);
78        let parse_config = self.configs.iter().map(ByteSizing::size).sum::<usize>();
79        parse_spec + parse_config
80    }
81}
82
83impl ByteSizing for StringPool {
84    fn size(&self) -> usize {
85        let size = Header::SIZE // header
86        + 5 * 4 //string_count, style_count, flags, string_offset, style_offset
87        + self.strings.len() * 4 // offsets array
88        + self.styles.len() * 4; // style offsets array
89        let string_length = self
90            .strings
91            .iter()
92            .map(|s| {
93                if self.use_utf8() {
94                    StringPool::utf8_string_size(s)
95                } else {
96                    StringPool::utf16_string_size(s)
97                }
98            })
99            .sum::<usize>();
100        let string_padding = padding(string_length);
101        let style_size = if self.styles.is_empty() {
102            0
103        } else {
104            self.styles.iter().map(ByteSizing::size).sum::<usize>() + 8 // 2 extra terminals
105        };
106        size + string_length + string_padding + style_size
107    }
108}
109
110impl StringPool {
111    pub(crate) fn utf8_string_size(string: &str) -> usize {
112        let char_count = string.chars().count();
113        let char_count_bytes = if char_count <= 0x7F { 1 } else { 2 };
114
115        let byte_count = string.len();
116        let byte_count_bytes = if byte_count <= 0x7F { 1 } else { 2 };
117
118        char_count_bytes + byte_count_bytes + byte_count + 1 // 1 is the null terminator
119    }
120
121    pub(crate) fn utf16_string_size(string: &str) -> usize {
122        let char_count = string.chars().count();
123        let char_count_bytes = if char_count <= 0x7FFF { 2 } else { 4 };
124
125        char_count_bytes + char_count * 2 + 2 // 2 is the null terminator
126    }
127}
128
129impl ByteSizing for Style {
130    fn size(&self) -> usize {
131        self.spans.len() * StyleSpan::SIZE + 4
132    }
133}
134
135impl ConstByteSizing for StyleSpan {
136    const SIZE: usize = 4 + 4 + 4;
137}
138
139impl ByteSizing for Package {
140    fn size(&self) -> usize {
141        Header::SIZE + 4 + 256 // header + id + package_name 
142        + 5 * 4 // _type_string_offset, _last_public_type, _key_string_offset, _last_public_key, _type_id_offset
143        + self.type_names.size()
144        + self.types.iter().map(ByteSizing::size).sum::<usize>()
145        + self.key_names.size()
146    }
147}
148
149impl ByteSizing for Arsc {
150    fn size(&self) -> usize {
151        Header::SIZE + 4 // header + package_count
152        + self.global_string_pool.size()
153        + self.packages.iter().map(ByteSizing::size).sum::<usize>()
154    }
155}
156
157impl ConstByteSizing for Header {
158    const SIZE: usize = 8;
159}
160
161/// Calculate the padding size for a given size
162///
163/// # Argument:
164/// * size - the size needs to be padded
165/// # Returns:
166/// the padding size with respect to 4
167pub(crate) fn padding(size: usize) -> usize {
168    (4 - size % 4) % 4
169}