diff --git a/crates/format/src/lib.rs b/crates/format/src/lib.rs index 4136a42f4..30f6e5489 100644 --- a/crates/format/src/lib.rs +++ b/crates/format/src/lib.rs @@ -254,14 +254,193 @@ fn image_index(images: &&BTreeMap, s: S) -> Re s.collect_seq(images.iter().map(|(hash, i)| Entry { hash, width: i.width, height: i.height })) } +/// A serializer adapter that writes `0` in place of every NaN or infinite float. JSON has no such +/// numbers (`serde_json` writes `null`, which the reader rejects), so one bad value must not stop +/// Save, autosave or recovery copies of the whole document. Everything else passes through. +struct Finite(S); + +/// Serializes a value through a [`Finite`] adapter around the serializer it is written with. +struct FiniteValue<'a, T: ?Sized>(&'a T); + +impl Serialize for FiniteValue<'_, T> { + fn serialize(&self, s: S) -> Result { + self.0.serialize(Finite(s)) + } +} + +macro_rules! finite_pass { + ($($name:ident($ty:ty)),* $(,)?) => { + $(fn $name(self, v: $ty) -> Result { self.0.$name(v) })* + }; +} + +impl Serializer for Finite { + type Ok = S::Ok; + type Error = S::Error; + type SerializeSeq = Finite; + type SerializeTuple = Finite; + type SerializeTupleStruct = Finite; + type SerializeTupleVariant = Finite; + type SerializeMap = Finite; + type SerializeStruct = Finite; + type SerializeStructVariant = Finite; + + finite_pass!( + serialize_bool(bool), + serialize_i8(i8), + serialize_i16(i16), + serialize_i32(i32), + serialize_i64(i64), + serialize_u8(u8), + serialize_u16(u16), + serialize_u32(u32), + serialize_u64(u64), + serialize_i128(i128), + serialize_u128(u128), + serialize_char(char), + serialize_str(&str), + serialize_bytes(&[u8]) + ); + + fn serialize_f32(self, v: f32) -> Result { + self.0.serialize_f32(if v.is_finite() { v } else { 0.0 }) + } + fn serialize_f64(self, v: f64) -> Result { + self.0.serialize_f64(if v.is_finite() { v } else { 0.0 }) + } + fn serialize_none(self) -> Result { + self.0.serialize_none() + } + fn serialize_some(self, v: &T) -> Result { + self.0.serialize_some(&FiniteValue(v)) + } + fn serialize_unit(self) -> Result { + self.0.serialize_unit() + } + fn serialize_unit_struct(self, name: &'static str) -> Result { + self.0.serialize_unit_struct(name) + } + fn serialize_unit_variant(self, name: &'static str, idx: u32, variant: &'static str) -> Result { + self.0.serialize_unit_variant(name, idx, variant) + } + fn serialize_newtype_struct(self, name: &'static str, v: &T) -> Result { + self.0.serialize_newtype_struct(name, &FiniteValue(v)) + } + fn serialize_newtype_variant(self, name: &'static str, idx: u32, variant: &'static str, v: &T) -> Result { + self.0.serialize_newtype_variant(name, idx, variant, &FiniteValue(v)) + } + fn serialize_seq(self, len: Option) -> Result { + self.0.serialize_seq(len).map(Finite) + } + fn serialize_tuple(self, len: usize) -> Result { + self.0.serialize_tuple(len).map(Finite) + } + fn serialize_tuple_struct(self, name: &'static str, len: usize) -> Result { + self.0.serialize_tuple_struct(name, len).map(Finite) + } + fn serialize_tuple_variant( + self, + name: &'static str, + idx: u32, + variant: &'static str, + len: usize, + ) -> Result { + self.0.serialize_tuple_variant(name, idx, variant, len).map(Finite) + } + fn serialize_map(self, len: Option) -> Result { + self.0.serialize_map(len).map(Finite) + } + fn serialize_struct(self, name: &'static str, len: usize) -> Result { + self.0.serialize_struct(name, len).map(Finite) + } + fn serialize_struct_variant( + self, + name: &'static str, + idx: u32, + variant: &'static str, + len: usize, + ) -> Result { + self.0.serialize_struct_variant(name, idx, variant, len).map(Finite) + } + fn is_human_readable(&self) -> bool { + self.0.is_human_readable() + } +} + +macro_rules! finite_elements { + ($($trait:ident::$method:ident),* $(,)?) => { + $(impl serde::ser::$trait for Finite { + type Ok = S::Ok; + type Error = S::Error; + fn $method(&mut self, v: &T) -> Result<(), S::Error> { + self.0.$method(&FiniteValue(v)) + } + fn end(self) -> Result { + self.0.end() + } + })* + }; +} + +finite_elements!( + SerializeSeq::serialize_element, + SerializeTuple::serialize_element, + SerializeTupleStruct::serialize_field, + SerializeTupleVariant::serialize_field +); + +impl serde::ser::SerializeMap for Finite { + type Ok = S::Ok; + type Error = S::Error; + fn serialize_key(&mut self, k: &T) -> Result<(), S::Error> { + self.0.serialize_key(&FiniteValue(k)) + } + fn serialize_value(&mut self, v: &T) -> Result<(), S::Error> { + self.0.serialize_value(&FiniteValue(v)) + } + fn end(self) -> Result { + self.0.end() + } +} + +impl serde::ser::SerializeStruct for Finite { + type Ok = S::Ok; + type Error = S::Error; + fn serialize_field(&mut self, key: &'static str, v: &T) -> Result<(), S::Error> { + self.0.serialize_field(key, &FiniteValue(v)) + } + fn skip_field(&mut self, key: &'static str) -> Result<(), S::Error> { + self.0.skip_field(key) + } + fn end(self) -> Result { + self.0.end() + } +} + +impl serde::ser::SerializeStructVariant for Finite { + type Ok = S::Ok; + type Error = S::Error; + fn serialize_field(&mut self, key: &'static str, v: &T) -> Result<(), S::Error> { + self.0.serialize_field(key, &FiniteValue(v)) + } + fn skip_field(&mut self, key: &'static str) -> Result<(), S::Error> { + self.0.skip_field(key) + } + fn end(self) -> Result { + self.0.end() + } +} + /// Serialize the document (without images) to the JSON value stored in `document.json`. +/// NaN and infinite numbers are written as `0`. pub fn to_json(doc: &Document) -> Result { - serde_json::to_value(DocumentJson::new(doc)).map_err(io_err) + DocumentJson::new(doc).serialize(Finite(serde_json::value::Serializer)).map_err(io_err) } /// Write the document (without images) as `document.json` content to `w`. +/// NaN and infinite numbers are written as `0`. pub fn write_json(doc: &Document, w: impl Write) -> Result<(), FormatError> { - serde_json::to_writer(w, &DocumentJson::new(doc)).map_err(io_err) + DocumentJson::new(doc).serialize(Finite(&mut serde_json::Serializer::new(w))).map_err(io_err) } /// Parse `document.json` content. @@ -840,6 +1019,37 @@ mod tests { assert!(load_bytes(&json).is_ok()); } + #[test] + fn non_finite_numbers_are_saved_as_zero() { + let mut d = Document::new("NaN"); + let p = d.first_page().unwrap(); + let r = d.create(p, NodeKind::Rectangle, "R", 1.0, 2.0, 3.0, 4.0).unwrap(); + assert!(save_bytes(&d, None).is_ok()); + d.get_mut(r).unwrap().width = f64::NAN; + d.get_mut(r).unwrap().opacity = f32::INFINITY; + // Saving never fails; non-finite numbers are written as 0 and the file reopens. + let bytes = save_bytes(&d, None).unwrap(); + let (back, _) = load_bytes(&bytes).unwrap(); + assert_eq!(back.node_count(), d.node_count()); + let n = back.get(r).unwrap(); + assert_eq!(n.width, 0.0); + assert_eq!(n.opacity, 0.0); + assert_eq!(n.height, 4.0); + let mut out = Vec::new(); + write_json(&d, &mut out).unwrap(); + let (back, _) = from_json(std::str::from_utf8(&out).unwrap()).unwrap(); + let n = back.get(r).unwrap(); + assert_eq!(n.width, 0.0); + assert_eq!(n.opacity, 0.0); + assert_eq!(n.height, 4.0); + let v = to_json(&d).unwrap(); + let (back, _) = from_json(&v.to_string()).unwrap(); + let n = back.get(r).unwrap(); + assert_eq!(n.width, 0.0); + assert_eq!(n.opacity, 0.0); + assert_eq!(n.height, 4.0); + } + #[test] fn garbage_is_an_error() { assert!(load_bytes(b"not a zip").is_err());