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}