Skip to main content

sdocx/
math.rs

1use crate::binary::Reader;
2use crate::frame::Frame;
3use crate::{BoundingBox, Error, ObjectMetadata, ObjectType, ParseLimits, Result, StoredObject};
4
5#[derive(Debug, Clone)]
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7#[non_exhaustive]
8pub struct MathMetadata {
9    pub base: ObjectMetadata,
10    pub editable: bool,
11    pub formula_objects: Vec<Vec<u8>>,
12    pub margins: Option<MathMargins>,
13    pub angle_type: Option<MathAngleType>,
14    pub connected_plot_uuids: Vec<String>,
15    pub property_mask: Vec<u8>,
16    pub field_mask: Vec<u8>,
17    pub fixed_trailing_data: Vec<u8>,
18    pub flexible_trailing_data: Vec<u8>,
19    pub trailing_data: Vec<u8>,
20}
21
22#[derive(Debug, Clone)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24#[non_exhaustive]
25pub struct PlotMetadata {
26    pub base: ObjectMetadata,
27    pub legacy_field_0: Option<u32>,
28    pub coordinate_rect: Option<BoundingBox>,
29    pub coordinate_color: Option<u32>,
30    pub background_color: Option<u32>,
31    pub graphs: Vec<PlotGraph>,
32    pub angle_type: Option<MathAngleType>,
33    pub property_mask: Vec<u8>,
34    pub field_mask: Vec<u8>,
35    pub fixed_trailing_data: Vec<u8>,
36    pub flexible_trailing_data: Vec<u8>,
37    pub trailing_data: Vec<u8>,
38}
39
40#[derive(Debug, Clone, PartialEq)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
42#[non_exhaustive]
43pub struct PlotGraph {
44    pub latex: String,
45    pub color: u32,
46    pub line_width: f32,
47    pub visibility_raw: u8,
48    pub substitution_latex: Vec<String>,
49}
50
51impl PlotGraph {
52    pub const fn is_visible(&self) -> bool {
53        self.visibility_raw == 1
54    }
55}
56
57#[derive(Debug, Clone, Copy, PartialEq)]
58#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
59#[non_exhaustive]
60pub struct MathMargins {
61    pub left: f64,
62    pub top: f64,
63    pub right: f64,
64    pub bottom: f64,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
69#[non_exhaustive]
70pub enum MathAngleType {
71    Degree,
72    Radian,
73    All,
74    Other(u32),
75}
76
77impl From<u32> for MathAngleType {
78    fn from(value: u32) -> Self {
79        match value {
80            0 => Self::Degree,
81            1 => Self::Radian,
82            2 => Self::All,
83            value => Self::Other(value),
84        }
85    }
86}
87
88impl StoredObject {
89    pub fn math_metadata(&self, page_bytes: &[u8]) -> Result<MathMetadata> {
90        self.math_metadata_with_limits(page_bytes, &ParseLimits::default())
91    }
92
93    pub fn math_metadata_with_limits(
94        &self,
95        page_bytes: &[u8],
96        limits: &ParseLimits,
97    ) -> Result<MathMetadata> {
98        let payload = checked_payload(self, page_bytes, ObjectType::Math, limits)?;
99        parse_math_metadata(payload, limits)
100    }
101
102    pub fn plot_metadata(&self, page_bytes: &[u8]) -> Result<PlotMetadata> {
103        self.plot_metadata_with_limits(page_bytes, &ParseLimits::default())
104    }
105
106    pub fn plot_metadata_with_limits(
107        &self,
108        page_bytes: &[u8],
109        limits: &ParseLimits,
110    ) -> Result<PlotMetadata> {
111        let payload = checked_payload(self, page_bytes, ObjectType::Plot, limits)?;
112        parse_plot_metadata(payload, limits)
113    }
114}
115
116pub(crate) fn checked_payload<'a>(
117    object: &StoredObject,
118    page_bytes: &'a [u8],
119    expected: ObjectType,
120    limits: &ParseLimits,
121) -> Result<&'a [u8]> {
122    if object.object_type != expected {
123        return Err(Error::Format(format!(
124            "expected a {expected:?} object, found {:?}",
125            object.object_type
126        )));
127    }
128    let payload = object
129        .payload(page_bytes)
130        .ok_or_else(|| Error::Format("math payload is outside its page".into()))?;
131    if payload.len() as u64 > limits.max_entry_size {
132        return Err(Error::LimitExceeded {
133            resource: "math payload size",
134            limit: limits.max_entry_size,
135            actual: payload.len() as u64,
136        });
137    }
138    Ok(payload)
139}
140
141fn parse_math_metadata(payload: &[u8], limits: &ParseLimits) -> Result<MathMetadata> {
142    let mut reader = Reader::new(payload, "math object");
143    let base = ObjectMetadata::read(&mut reader)?;
144    let frame = Frame::read(&mut reader)?;
145    frame.expect_kind(21)?;
146    let mut flexible = Reader::new(frame.flexible, "math flexible fields");
147    let mut entries = 0;
148    let mut formula_objects = Vec::new();
149    if frame.fields.contains(0) {
150        let count = read_count(&mut flexible, 4, &mut entries, limits)?;
151        formula_objects.reserve(count);
152        for _ in 0..count {
153            let size = flexible.read_u32("formula object size")? as usize;
154            formula_objects.push(flexible.read_bytes(size, "formula object")?.to_vec());
155        }
156    }
157    let margins = frame
158        .fields
159        .contains(1)
160        .then(|| {
161            let values = crate::object::read_bbox(&mut flexible)?;
162            Ok::<_, Error>(MathMargins {
163                left: values.x_min,
164                top: values.y_min,
165                right: values.x_max,
166                bottom: values.y_max,
167            })
168        })
169        .transpose()?;
170    let angle_type = frame
171        .fields
172        .contains(2)
173        .then(|| flexible.read_u32("math angle type").map(Into::into))
174        .transpose()?;
175    let mut connected_plot_uuids = Vec::new();
176    if frame.fields.contains(3) {
177        let count = read_count(&mut flexible, 2, &mut entries, limits)?;
178        connected_plot_uuids.reserve(count);
179        for _ in 0..count {
180            connected_plot_uuids.push(flexible.read_utf8_u16("connected plot UUID")?);
181        }
182    }
183    Ok(MathMetadata {
184        base,
185        editable: frame.properties.contains(0),
186        formula_objects,
187        margins,
188        angle_type,
189        connected_plot_uuids,
190        property_mask: frame.properties.bytes().to_vec(),
191        field_mask: frame.fields.bytes().to_vec(),
192        fixed_trailing_data: frame.fixed.to_vec(),
193        flexible_trailing_data: flexible
194            .read_bytes(flexible.remaining(), "math flexible trailing data")?
195            .to_vec(),
196        trailing_data: reader
197            .read_bytes(reader.remaining(), "math trailing data")?
198            .to_vec(),
199    })
200}
201
202pub(crate) fn read_count(
203    reader: &mut Reader<'_>,
204    minimum_record_size: usize,
205    entries: &mut u64,
206    limits: &ParseLimits,
207) -> Result<usize> {
208    let count = reader.read_u32("math entry count")? as usize;
209    *entries += count as u64;
210    if *entries > limits.max_objects_per_page as u64 {
211        return Err(Error::LimitExceeded {
212            resource: "math entries",
213            limit: limits.max_objects_per_page as u64,
214            actual: *entries,
215        });
216    }
217    if count > reader.remaining() / minimum_record_size {
218        return Err(Error::Format(
219            "math entry count exceeds its bounded payload".into(),
220        ));
221    }
222    Ok(count)
223}
224
225fn parse_plot_metadata(payload: &[u8], limits: &ParseLimits) -> Result<PlotMetadata> {
226    let mut reader = Reader::new(payload, "plot object");
227    let base = ObjectMetadata::read(&mut reader)?;
228    let frame = Frame::read(&mut reader)?;
229    frame.expect_kind(20)?;
230    let mut flexible = Reader::new(frame.flexible, "plot flexible fields");
231    let legacy_field_0 = frame
232        .fields
233        .contains(0)
234        .then(|| flexible.read_u32("legacy plot field 0"))
235        .transpose()?;
236    let coordinate_rect = frame
237        .fields
238        .contains(1)
239        .then(|| crate::object::read_bbox(&mut flexible))
240        .transpose()?;
241    let coordinate_color = frame
242        .fields
243        .contains(2)
244        .then(|| flexible.read_u32("plot coordinate color"))
245        .transpose()?;
246    let background_color = frame
247        .fields
248        .contains(3)
249        .then(|| flexible.read_u32("plot background color"))
250        .transpose()?;
251    let mut entries = 0;
252    let mut graphs = Vec::new();
253    if frame.fields.contains(4) {
254        let count = read_count(&mut flexible, 15, &mut entries, limits)?;
255        graphs.reserve(count);
256        for _ in 0..count {
257            let latex = read_latex(&mut flexible, limits)?;
258            let color = flexible.read_u32("graph color")?;
259            let line_width = flexible.read_f32("graph line width")?;
260            let visibility_raw = flexible.read_u8("graph visibility")?;
261            let count = read_count(&mut flexible, 2, &mut entries, limits)?;
262            let mut substitution_latex = Vec::with_capacity(count);
263            for _ in 0..count {
264                substitution_latex.push(read_latex(&mut flexible, limits)?);
265            }
266            graphs.push(PlotGraph {
267                latex,
268                color,
269                line_width,
270                visibility_raw,
271                substitution_latex,
272            });
273        }
274    }
275    let angle_type = frame
276        .fields
277        .contains(5)
278        .then(|| flexible.read_u32("plot angle type").map(Into::into))
279        .transpose()?;
280    Ok(PlotMetadata {
281        base,
282        legacy_field_0,
283        coordinate_rect,
284        coordinate_color,
285        background_color,
286        graphs,
287        angle_type,
288        property_mask: frame.properties.bytes().to_vec(),
289        field_mask: frame.fields.bytes().to_vec(),
290        fixed_trailing_data: frame.fixed.to_vec(),
291        flexible_trailing_data: flexible
292            .read_bytes(flexible.remaining(), "plot flexible trailing data")?
293            .to_vec(),
294        trailing_data: reader
295            .read_bytes(reader.remaining(), "plot trailing data")?
296            .to_vec(),
297    })
298}
299
300pub(crate) fn read_latex(reader: &mut Reader<'_>, limits: &ParseLimits) -> Result<String> {
301    let value = reader.read_utf8_u16("LaTeX")?;
302    let units = value.encode_utf16().count();
303    if units > limits.max_text_characters {
304        return Err(Error::LimitExceeded {
305            resource: "text characters",
306            limit: limits.max_text_characters as u64,
307            actual: units as u64,
308        });
309    }
310    Ok(value)
311}