diff --git a/src/base/array_storage.rs b/src/base/array_storage.rs index 3bc71e1a3..53f8efbe7 100644 --- a/src/base/array_storage.rs +++ b/src/base/array_storage.rs @@ -1,4 +1,4 @@ -use std::fmt::{self, Debug, Formatter}; +use std::fmt::Debug; // use std::hash::{Hash, Hasher}; use std::ops::Mul; @@ -9,6 +9,8 @@ use serde::ser::SerializeSeq; #[cfg(feature = "serde-serialize-no-std")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; #[cfg(feature = "serde-serialize-no-std")] +use std::fmt::{self, Formatter}; +#[cfg(feature = "serde-serialize-no-std")] use std::marker::PhantomData; use crate::base::allocator::Allocator; @@ -26,7 +28,7 @@ use std::mem; */ /// A array-based statically sized matrix data storage. #[repr(transparent)] -#[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] #[cfg_attr( feature = "rkyv-serialize-no-std", @@ -62,13 +64,6 @@ where } } -impl Debug for ArrayStorage { - #[inline] - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - self.0.fmt(fmt) - } -} - unsafe impl RawStorage, Const> for ArrayStorage { diff --git a/src/base/matrix.rs b/src/base/matrix.rs index 8f8786c13..7998ff22c 100644 --- a/src/base/matrix.rs +++ b/src/base/matrix.rs @@ -195,9 +195,32 @@ pub struct Matrix { _phantoms: PhantomData<(T, R, C)>, } -impl fmt::Debug for Matrix { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - self.data.fmt(formatter) +impl + fmt::Debug> fmt::Debug + for Matrix +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + if f.alternate() { + writeln!(f, "")?; + } + write!(f, "[")?; + + let row_separator = match f.alternate() { + true => ";\n ", + false => "; ", + }; + + for (i, row) in self.row_iter().enumerate() { + if i > 0 { + write!(f, "{row_separator}")?; + } + for (j, element) in row.iter().enumerate() { + if j > 0 { + write!(f, ", ")?; + } + element.fmt(f)?; + } + } + write!(f, "]") } } @@ -1855,68 +1878,94 @@ macro_rules! impl_fmt { S: RawStorage, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - #[cfg(feature = "std")] - fn val_width(val: &T, f: &mut fmt::Formatter<'_>) -> usize { - match f.precision() { - Some(precision) => format!($fmt_str_with_precision, val, precision) - .chars() - .count(), - None => format!($fmt_str_without_precision, val).chars().count(), + if !f.alternate() { + // pretty print in 2D layout + #[cfg(feature = "std")] + fn val_width(val: &T, f: &mut fmt::Formatter<'_>) -> usize { + match f.precision() { + Some(precision) => format!($fmt_str_with_precision, val, precision) + .chars() + .count(), + None => format!($fmt_str_without_precision, val).chars().count(), + } } - } - #[cfg(not(feature = "std"))] - fn val_width(_: &T, _: &mut fmt::Formatter<'_>) -> usize { - 4 - } + #[cfg(not(feature = "std"))] + fn val_width(_: &T, _: &mut fmt::Formatter<'_>) -> usize { + 4 + } - let (nrows, ncols) = self.shape(); + let (nrows, ncols) = self.shape(); - if nrows == 0 || ncols == 0 { - return write!(f, "[ ]"); - } + if nrows == 0 || ncols == 0 { + return write!(f, "[ ]"); + } + + let mut max_length = 0; - let mut max_length = 0; + for i in 0..nrows { + for j in 0..ncols { + max_length = crate::max(max_length, val_width(&self[(i, j)], f)); + } + } - for i in 0..nrows { - for j in 0..ncols { - max_length = crate::max(max_length, val_width(&self[(i, j)], f)); + let max_length_with_space = max_length + 1; + + writeln!(f)?; // leading newline to ensure no offset from previous prints + writeln!( + f, + " ┌ {:>width$} ┐", + "", + width = max_length_with_space * ncols - 1 + )?; + + for i in 0..nrows { + write!(f, " │")?; + for j in 0..ncols { + let number_length = val_width(&self[(i, j)], f) + 1; + let pad = max_length_with_space - number_length; + write!(f, " {:>thepad$}", "", thepad = pad)?; + match f.precision() { + Some(precision) => { + write!(f, $fmt_str_with_precision, (*self)[(i, j)], precision)? + } + None => write!(f, $fmt_str_without_precision, (*self)[(i, j)])?, + } + } + writeln!(f, " │")?; } - } - let max_length_with_space = max_length + 1; - - writeln!(f)?; - writeln!( - f, - " ┌ {:>width$} ┐", - "", - width = max_length_with_space * ncols - 1 - )?; - - for i in 0..nrows { - write!(f, " │")?; - for j in 0..ncols { - let number_length = val_width(&self[(i, j)], f) + 1; - let pad = max_length_with_space - number_length; - write!(f, " {:>thepad$}", "", thepad = pad)?; - match f.precision() { - Some(precision) => { - write!(f, $fmt_str_with_precision, (*self)[(i, j)], precision)? + write!( + f, + " └ {:>width$} ┘", + "", + width = max_length_with_space * ncols - 1 + ) + } else { + // print on single line with semicolon-delimited rows and comma-delimited columns + let (nrows, ncols) = self.shape(); + write!(f, "[")?; + for row in 0..nrows { + for col in 0..ncols { + if col != 0 { + write!(f, ", ")?; } - None => write!(f, $fmt_str_without_precision, (*self)[(i, j)])?, + match f.precision() { + Some(precision) => write!( + f, + $fmt_str_with_precision, + (*self)[(row, col)], + precision + )?, + None => write!(f, $fmt_str_without_precision, (*self)[(row, col)])?, + } + } + if row != nrows - 1 { + write!(f, "; ")?; } } - writeln!(f, " │")?; + write!(f, "]") } - - writeln!( - f, - " └ {:>width$} ┘", - "", - width = max_length_with_space * ncols - 1 - )?; - writeln!(f) } } }; @@ -1930,6 +1979,28 @@ impl_fmt!(fmt::UpperHex, "{:X}", "{:1$X}"); impl_fmt!(fmt::Binary, "{:b}", "{:.1$b}"); impl_fmt!(fmt::Pointer, "{:p}", "{:.1$p}"); +/// Displays a vector using commas as the delimiter +pub fn format_column_vec_as_row( + vector: &OVector, + f: &mut fmt::Formatter<'_>, +) -> fmt::Result +where + DefaultAllocator: Allocator, +{ + if vector.is_empty() { + return write!(f, "[ ]"); + } + + write!(f, "[")?; + let mut it = vector.iter(); + std::fmt::Display::fmt(it.next().unwrap(), f)?; + for comp in it { + write!(f, ", ")?; + std::fmt::Display::fmt(comp, f)?; + } + write!(f, "]") +} + #[cfg(test)] mod tests { #[test] @@ -1948,9 +2019,7 @@ mod tests { ┌ ┐ │ 1e6 2e5 │ │ 2e-5 1e0 │ - └ ┘ - -" + └ ┘" ) } } diff --git a/src/geometry/dual_quaternion.rs b/src/geometry/dual_quaternion.rs index 6f1b70536..adb6d9eaa 100644 --- a/src/geometry/dual_quaternion.rs +++ b/src/geometry/dual_quaternion.rs @@ -951,25 +951,12 @@ impl Default for UnitDualQuaternion { impl fmt::Display for UnitDualQuaternion { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(axis) = self.rotation().axis() { - let axis = axis.into_inner(); - write!( - f, - "UnitDualQuaternion translation: {} − angle: {} − axis: ({}, {}, {})", - self.translation().vector, - self.rotation().angle(), - axis[0], - axis[1], - axis[2] - ) - } else { - write!( - f, - "UnitDualQuaternion translation: {} − angle: {} − axis: (undefined)", - self.translation().vector, - self.rotation().angle() - ) - } + write!(f, "{{ translation: ")?; + crate::format_column_vec_as_row(&self.translation().vector, f)?; + write!(f, ", ")?; + write!(f, "rotation: ")?; + std::fmt::Display::fmt(&self.rotation(), f)?; + write!(f, " }}") } } diff --git a/src/geometry/isometry.rs b/src/geometry/isometry.rs index 921697427..5db33bc3b 100755 --- a/src/geometry/isometry.rs +++ b/src/geometry/isometry.rs @@ -548,11 +548,11 @@ where R: fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let precision = f.precision().unwrap_or(3); - - writeln!(f, "Isometry {{")?; - write!(f, "{:.*}", precision, self.translation)?; - write!(f, "{:.*}", precision, self.rotation)?; - writeln!(f, "}}") + write!(f, "{{ translation: ")?; + crate::format_column_vec_as_row(&self.translation.vector, f)?; + write!(f, ", ")?; + write!(f, "rotation: ")?; + std::fmt::Display::fmt(&self.rotation, f)?; + write!(f, " }}") } } diff --git a/src/geometry/point.rs b/src/geometry/point.rs index 306c18e5e..dce84ed46 100644 --- a/src/geometry/point.rs +++ b/src/geometry/point.rs @@ -457,17 +457,19 @@ impl fmt::Display for OPoint where DefaultAllocator: Allocator, { + /// ```rust + /// # use nalgebra::Point3; + /// let point = Point3::new(1.12345678, 2.12345678, 3.12345678); + /// let rounded = format!("{:#.4}", point); // print point in compact representation + /// assert_eq!(rounded, "[1.1235, 2.1235, 3.1235]"); + /// let vertical = format!("{}", point); // print point as a column + /// assert_eq!(vertical, "\n ┌ ┐\n │ 1.12345678 │\n │ 2.12345678 │\n │ 3.12345678 │\n └ ┘"); + /// ``` fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{{")?; - - let mut it = self.coords.iter(); - - write!(f, "{}", *it.next().unwrap())?; - - for comp in it { - write!(f, ", {}", *comp)?; + if f.alternate() { + crate::format_column_vec_as_row(&self.coords, f) + } else { + std::fmt::Display::fmt(&self.coords, f) // pretty-prints vector } - - write!(f, "}}") } } diff --git a/src/geometry/quaternion.rs b/src/geometry/quaternion.rs index f38dca6f7..0898cfc29 100755 --- a/src/geometry/quaternion.rs +++ b/src/geometry/quaternion.rs @@ -36,7 +36,7 @@ pub struct Quaternion { impl fmt::Debug for Quaternion { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - self.coords.as_slice().fmt(formatter) + formatter.debug_list().entries(self.coords.iter()).finish() } } @@ -995,11 +995,16 @@ impl> UlpsEq for Quaternion { impl fmt::Display for Quaternion { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Quaternion {} − ({}, {}, {})", - self[3], self[0], self[1], self[2] - ) + // Formatting of each item is forwarded to standard fmt display so that formatting + // flags are followed correctly. + std::fmt::Display::fmt(&self[0], f)?; + write!(f, " + ")?; + std::fmt::Display::fmt(&self[1], f)?; + write!(f, "i + ")?; + std::fmt::Display::fmt(&self[2], f)?; + write!(f, "j + ")?; + std::fmt::Display::fmt(&self[3], f)?; + write!(f, "k") } } @@ -1636,23 +1641,22 @@ impl Default for UnitQuaternion { impl fmt::Display for UnitQuaternion { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Formatting of each item is forwarded to standard fmt Display so that formatting + // flags are followed correctly. + write!(f, "{{ angle: ")?; + std::fmt::Display::fmt(&self.angle(), f)?; + write!(f, ", axis: (")?; if let Some(axis) = self.axis() { let axis = axis.into_inner(); - write!( - f, - "UnitQuaternion angle: {} − axis: ({}, {}, {})", - self.angle(), - axis[0], - axis[1], - axis[2] - ) + std::fmt::Display::fmt(&axis[0], f)?; + write!(f, ", ")?; + std::fmt::Display::fmt(&axis[1], f)?; + write!(f, ", ")?; + std::fmt::Display::fmt(&axis[2], f)?; } else { - write!( - f, - "UnitQuaternion angle: {} − axis: (undefined)", - self.angle() - ) + write!(f, "undefined")?; } + write!(f, ") }}") } } diff --git a/src/geometry/scale.rs b/src/geometry/scale.rs index 37da1ef04..4a06ac6c7 100755 --- a/src/geometry/scale.rs +++ b/src/geometry/scale.rs @@ -369,10 +369,10 @@ where */ impl fmt::Display for Scale { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let precision = f.precision().unwrap_or(3); - - writeln!(f, "Scale {{")?; - write!(f, "{:.*}", precision, self.vector)?; - writeln!(f, "}}") + if f.alternate() { + crate::format_column_vec_as_row(&self.vector, f) + } else { + std::fmt::Display::fmt(&self.vector, f) // pretty-prints vector + } } } diff --git a/src/geometry/similarity.rs b/src/geometry/similarity.rs index 8c38ff1e6..cb5e32f09 100755 --- a/src/geometry/similarity.rs +++ b/src/geometry/similarity.rs @@ -397,11 +397,13 @@ where R: AbstractRotation + fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let precision = f.precision().unwrap_or(3); - - writeln!(f, "Similarity {{")?; - write!(f, "{:.*}", precision, self.isometry)?; - write!(f, "Scaling: {:.*}", precision, self.scaling)?; - writeln!(f, "}}") + write!(f, "{{ translation: ")?; + crate::format_column_vec_as_row(&self.isometry.translation.vector, f)?; + write!(f, ", ")?; + write!(f, "rotation: ")?; + std::fmt::Display::fmt(&self.isometry.rotation, f)?; + write!(f, ", scaling: ")?; + std::fmt::Display::fmt(&self.scaling, f)?; + write!(f, " }}") } } diff --git a/src/geometry/translation.rs b/src/geometry/translation.rs index bef66f686..2cbff1a06 100755 --- a/src/geometry/translation.rs +++ b/src/geometry/translation.rs @@ -284,10 +284,10 @@ where */ impl fmt::Display for Translation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let precision = f.precision().unwrap_or(3); - - writeln!(f, "Translation {{")?; - write!(f, "{:.*}", precision, self.vector)?; - writeln!(f, "}}") + if f.alternate() { + crate::format_column_vec_as_row(&self.vector, f) + } else { + std::fmt::Display::fmt(&self.vector, f) // pretty-prints vector + } } } diff --git a/tests/core/matrix.rs b/tests/core/matrix.rs index 8a545e973..8030b47ea 100644 --- a/tests/core/matrix.rs +++ b/tests/core/matrix.rs @@ -78,16 +78,33 @@ fn iter() { } #[test] -fn debug_output_corresponds_to_data_container() { +fn debug_output_semicolon_delimited_rowwise() { let m = Matrix2::new(1.0, 2.0, 3.0, 4.0); - let output_stable = "[[1, 3], [2, 4]]"; // Current output on the stable channel. - let output_nightly = "[[1.0, 3.0], [2.0, 4.0]]"; // Current output on the nightly channel. + let expected_output = "[1.0, 2.0; 3.0, 4.0]"; let current_output = format!("{:?}", m); - dbg!(output_stable); - dbg!(output_nightly); + dbg!(expected_output); dbg!(¤t_output); - assert!(current_output == output_stable || current_output == output_nightly); + assert!(current_output == expected_output); +} + +#[test] +fn format_empty_matrix() { + let empty_row: [f32; 0] = []; + let m = na::DMatrix::from_row_slice(0, 0, &empty_row); + let expected_output_display = "[ ]"; + let expected_output_debug = "[]"; + let current_output_display = format!("{}", m); + let current_output_debug = format!("{:?}", m); + dbg!(expected_output_display); + dbg!(expected_output_debug); + dbg!(¤t_output_debug); + dbg!(¤t_output_display); + + assert!( + current_output_display == expected_output_display + && current_output_debug == expected_output_debug + ); } #[test]