work on tablelooks
authorBen Pfaff <blp@cs.stanford.edu>
Sun, 9 Mar 2025 19:51:51 +0000 (12:51 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 9 Mar 2025 19:51:51 +0000 (12:51 -0700)
rust/pspp/src/output/pivot/mod.rs

index b08d83d8dc277ce5eb14c57fe2d7317d2604b6db..6dcded30cd3885429cc7ce61c4c208dd1a4a4764 100644 (file)
@@ -510,6 +510,7 @@ impl CategoryTrait for Category {
 pub struct Look {
     pub name: Option<String>,
 
+    /// Whether to hide rows or columns whose cells are all empty.
     pub omit_empty: bool,
 
     pub row_label_position: RowLabelPosition,
@@ -588,16 +589,18 @@ pub enum RowLabelPosition {
 mod look_xml {
     use std::{fmt::Debug, num::ParseFloatError, str::FromStr};
 
+    use enum_map::enum_map;
     use serde::{de::Visitor, Deserialize};
 
     use crate::output::pivot::{
-        Color, FootnoteMarkerPosition, FootnoteMarkerType, RowLabelPosition, Stroke,
+        Area, AreaStyle, Axis2, Color, FootnoteMarkerPosition, FootnoteMarkerType, HeadingRegion,
+        HorzAlign, Look, RowLabelPosition, Stroke, VertAlign,
     };
     use thiserror::Error as ThisError;
 
     #[derive(Deserialize, Debug)]
     #[serde(rename_all = "camelCase")]
-    struct TableProperties {
+    pub struct TableProperties {
         #[serde(rename = "@name")]
         name: Option<String>,
         general_properties: GeneralProperties,
@@ -607,6 +610,60 @@ mod look_xml {
         printing_properties: PrintingProperties,
     }
 
+    impl From<TableProperties> for Look {
+        fn from(table_properties: TableProperties) -> Self {
+            Self {
+                name: table_properties.name,
+                omit_empty: table_properties.general_properties.hide_empty_rows,
+                row_label_position: table_properties.general_properties.row_label_position,
+                heading_widths: enum_map! {
+                    HeadingRegion::ColumnHeadings => table_properties.general_properties.minimum_column_width..=table_properties.general_properties.maximum_column_width,
+                    HeadingRegion::RowHeadings => table_properties.general_properties.minimum_row_width..=table_properties.general_properties.maximum_row_width,
+                }.map(|_k, r|(*r.start()).try_into().unwrap_or_default()..=(*r.end()).try_into().unwrap_or_default()),
+                footnote_marker_type: table_properties.footnote_properties.marker_type,
+                footnote_marker_position: table_properties.footnote_properties.marker_position,
+                areas: enum_map! {
+                    Area::Title => table_properties.cell_format_properties.title.style.as_area_style(),
+                    Area::Caption => table_properties.cell_format_properties.caption.style.as_area_style(),
+                    Area::Footer => table_properties.cell_format_properties.footnotes.style.as_area_style(),
+                    Area::Corner => table_properties.cell_format_properties.corner_labels.style.as_area_style(),
+                    Area::ColumnLabels => table_properties.cell_format_properties.column_labels.style.as_area_style(),
+                    Area::RowLabels => table_properties.cell_format_properties.row_labels.style.as_area_style(),
+                    Area::Data => table_properties.cell_format_properties.data.style.as_area_style(),
+                    Area::Layers => table_properties.cell_format_properties.layers.style.as_area_style(),
+                },
+                borders: todo!(),
+                print_all_layers: table_properties.printing_properties.print_all_layers,
+                paginate_layers: table_properties
+                    .printing_properties
+                    .print_each_layer_on_separate_page,
+                shrink_to_fit: enum_map! {
+                    Axis2::X => table_properties.printing_properties.rescale_wide_table_to_fit_page,
+                    Axis2::Y => table_properties.printing_properties.rescale_long_table_to_fit_page,
+                },
+                top_continuation: table_properties
+                    .printing_properties
+                    .continuation_text_at_top,
+                bottom_continuation: table_properties
+                    .printing_properties
+                    .continuation_text_at_bottom,
+                continuation: {
+                    let text = table_properties.printing_properties.continuation_text;
+                    if text == "" {
+                        None
+                    } else {
+                        Some(text)
+                    }
+                },
+                n_orphan_lines: table_properties
+                    .printing_properties
+                    .window_orphan_lines
+                    .try_into()
+                    .unwrap_or_default(),
+            }
+        }
+    }
+
     #[derive(Deserialize, Debug)]
     struct GeneralProperties {
         #[serde(rename = "@hideEmptyRows")]
@@ -625,7 +682,7 @@ mod look_xml {
         minimum_row_width: i64,
 
         #[serde(rename = "@rowDimensionLabels")]
-        row_dimension_labels: RowLabelPosition,
+        row_label_position: RowLabelPosition,
     }
 
     #[derive(Deserialize, Debug)]
@@ -635,7 +692,7 @@ mod look_xml {
         marker_position: FootnoteMarkerPosition,
 
         #[serde(rename = "@numberFormat")]
-        number_format: FootnoteMarkerType,
+        marker_type: FootnoteMarkerType,
     }
 
     #[derive(Deserialize, Debug)]
@@ -694,7 +751,51 @@ mod look_xml {
         decimal_offset: Dimension,
     }
 
-    #[derive(Deserialize, Debug, Default)]
+    impl CellStyle {
+        fn as_area_style(&self) -> AreaStyle {
+            AreaStyle {
+                cell_style: super::CellStyle {
+                    horz_align: match self.text_alignment {
+                        TextAlignment::Left => Some(HorzAlign::Left),
+                        TextAlignment::Right => Some(HorzAlign::Right),
+                        TextAlignment::Center => Some(HorzAlign::Center),
+                        TextAlignment::Decimal => Some(HorzAlign::Decimal {
+                            offset: self.decimal_offset.as_px_f64(),
+                            c: '.',
+                        }),
+                        TextAlignment::Mixed => None,
+                    },
+                    vert_align: match self.label_location_vertical {
+                        LabelLocationVertical::Positive => VertAlign::Top,
+                        LabelLocationVertical::Negative => VertAlign::Bottom,
+                        LabelLocationVertical::Center => VertAlign::Middle,
+                    },
+                    margins: enum_map! {
+                        Axis2::X => [self.margin_left.as_px_i32(), self.margin_right.as_px_i32()],
+                        Axis2::Y => [self.margin_top.as_px_i32(), self.margin_bottom.as_px_i32()],
+                    },
+                },
+                font_style: super::FontStyle {
+                    bold: self.font_weight == FontWeight::Bold,
+                    italic: self.font_style == FontStyle::Italic,
+                    underline: self.font_underline == FontUnderline::Underline,
+                    markup: false,
+                    font: self.font_family.clone(),
+                    fg: [
+                        self.color.unwrap_or(Color::BLACK),
+                        self.alternating_text_color.unwrap_or(Color::BLACK),
+                    ],
+                    bg: [
+                        self.color2.unwrap_or(Color::BLACK),
+                        self.alternating_color.unwrap_or(Color::BLACK),
+                    ],
+                    size: self.font_size.as_pt_i32(),
+                },
+            }
+        }
+    }
+
+    #[derive(Deserialize, Debug, Default, PartialEq, Eq)]
     #[serde(rename_all = "camelCase")]
     enum FontStyle {
         #[default]
@@ -702,7 +803,7 @@ mod look_xml {
         Italic,
     }
 
-    #[derive(Deserialize, Debug, Default)]
+    #[derive(Deserialize, Debug, Default, PartialEq, Eq)]
     #[serde(rename_all = "camelCase")]
     enum FontWeight {
         #[default]
@@ -710,7 +811,7 @@ mod look_xml {
         Bold,
     }
 
-    #[derive(Deserialize, Debug, Default)]
+    #[derive(Deserialize, Debug, Default, PartialEq, Eq)]
     #[serde(rename_all = "camelCase")]
     enum FontUnderline {
         #[default]
@@ -804,12 +905,27 @@ mod look_xml {
         continuation_text_at_top: bool,
     }
 
-    #[derive(Default, PartialEq)]
+    #[derive(Copy, Clone, Default, PartialEq)]
     struct Dimension(
         /// In inches.
         f64,
     );
 
+    impl Dimension {
+        fn as_px_f64(self) -> f64 {
+            self.0 * 96.0
+        }
+        fn as_px_i32(self) -> i32 {
+            num::cast(self.as_px_f64() + 0.5).unwrap_or_default()
+        }
+        fn as_pt_f64(self) -> f64 {
+            self.0 * 72.0
+        }
+        fn as_pt_i32(self) -> i32 {
+            num::cast(self.as_pt_f64() + 0.5).unwrap_or_default()
+        }
+    }
+
     impl Debug for Dimension {
         fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
             write!(f, "{:.2}in", self.0)