more work on text driver
authorBen Pfaff <blp@cs.stanford.edu>
Sun, 2 Mar 2025 23:07:42 +0000 (15:07 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 2 Mar 2025 23:07:42 +0000 (15:07 -0800)
rust/pspp/src/output/pivot/mod.rs
rust/pspp/src/output/text.rs

index d6f81083054eaf00f813bb94060ef0e8ffe08bc9..971d9392d4a055a94e1d889d04363ef78a882026 100644 (file)
@@ -74,6 +74,7 @@ use smallvec::{smallvec, SmallVec};
 use crate::{
     dictionary::Value as DataValue,
     format::{Format, Settings as FormatSettings, Type, UncheckedFormat},
+    raw::VarType,
     settings::{Settings, Show},
 };
 
@@ -104,19 +105,19 @@ impl Area {
     fn default_cell_style(self) -> CellStyle {
         use HorzAlign::*;
         use VertAlign::*;
-        let (halign, valign, hmargins, vmargins) = match self {
-            Area::Title => (Center, Middle, [8, 11], [1, 8]),
-            Area::Caption => (Left, Top, [8, 11], [1, 1]),
-            Area::Footer => (Left, Top, [11, 8], [2, 3]),
-            Area::Corner => (Left, Bottom, [8, 11], [1, 1]),
-            Area::ColumnLabels => (Left, Top, [8, 11], [1, 3]),
-            Area::RowLabels => (Left, Top, [8, 11], [1, 3]),
-            Area::Data => (Mixed, Top, [8, 11], [1, 1]),
-            Area::Layers => (Left, Bottom, [8, 11], [1, 3]),
+        let (horz_align, vert_align, hmargins, vmargins) = match self {
+            Area::Title => (Some(Center), Middle, [8, 11], [1, 8]),
+            Area::Caption => (Some(Left), Top, [8, 11], [1, 1]),
+            Area::Footer => (Some(Left), Top, [11, 8], [2, 3]),
+            Area::Corner => (Some(Left), Bottom, [8, 11], [1, 1]),
+            Area::ColumnLabels => (Some(Left), Top, [8, 11], [1, 3]),
+            Area::RowLabels => (Some(Left), Top, [8, 11], [1, 3]),
+            Area::Data => (None, Top, [8, 11], [1, 1]),
+            Area::Layers => (Some(Left), Bottom, [8, 11], [1, 3]),
         };
         CellStyle {
-            horz_align: halign,
-            vert_align: valign,
+            horz_align,
+            vert_align,
             margins: enum_map! { Axis2::X => hmargins, Axis2::Y => vmargins },
         }
     }
@@ -598,7 +599,9 @@ pub struct AreaStyle {
 
 #[derive(Clone, Debug)]
 pub struct CellStyle {
-    pub horz_align: HorzAlign,
+    /// `None` means "mixed" alignment: align strings to the left, numbers to
+    /// the right.
+    pub horz_align: Option<HorzAlign>,
     pub vert_align: VertAlign,
 
     /// Margins in 1/96" units.
@@ -621,9 +624,6 @@ pub enum HorzAlign {
     /// Centered.
     Center,
 
-    /// Align strings to the left, other formats to the right.
-    Mixed,
-
     /// Align the decimal point at the specified position.
     Decimal {
         /// Decimal offset from the right side of the cell, in 1/96" units.
@@ -634,6 +634,15 @@ pub enum HorzAlign {
     },
 }
 
+impl HorzAlign {
+    fn for_mixed(var_type: VarType) -> Self {
+        match var_type {
+            VarType::Numeric => Self::Right,
+            VarType::String => Self::Left,
+        }
+    }
+}
+
 #[derive(Copy, Clone, Debug)]
 pub enum VertAlign {
     /// Top alignment.
@@ -1215,6 +1224,8 @@ pub struct DisplayValue<'a> {
     subscripts: &'a [String],
     footnotes: &'a [Arc<Footnote>],
     options: ValueOptions,
+    show_value: bool,
+    show_label: Option<&'a str>,
 }
 
 impl<'a> DisplayValue<'a> {
@@ -1250,30 +1261,17 @@ impl<'a> DisplayValue<'a> {
         Self { footnotes, ..self }
     }
 
-    fn show(&self) -> (bool, Option<&str>) {
-        if let Some(value_label) = self.inner.value_label() {
-            interpret_show(
-                || Settings::global().show_values,
-                self.options.show_values,
-                self.inner.show(),
-                value_label,
-            )
-        } else if let Some(variable_label) = self.inner.variable_label() {
-            interpret_show(
-                || Settings::global().show_variables,
-                self.options.show_variables,
-                self.inner.show(),
-                variable_label,
-            )
-        } else {
-            (true, None)
-        }
-    }
-
     fn small(&self) -> f64 {
         self.options.small
     }
 
+    fn var_type(&self) -> VarType {
+        match self.inner {
+            ValueInner::Number { .. } if self.show_label.is_none() => VarType::Numeric,
+            _ => VarType::String,
+        }
+    }
+
     fn template(
         &self,
         f: &mut std::fmt::Formatter<'_>,
@@ -1395,7 +1393,6 @@ fn interpret_show(
 
 impl<'a, 'b> Display for DisplayValue<'a> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let (show_value, label) = self.show();
         match self.inner {
             ValueInner::Number {
                 format,
@@ -1403,7 +1400,7 @@ impl<'a, 'b> Display for DisplayValue<'a> {
                 value,
                 ..
             } => {
-                if show_value {
+                if self.show_value {
                     let format = if format.type_() == Type::F
                         && *honor_small
                         && value.is_some_and(|value| value != 0.0 && value.abs() < self.small())
@@ -1421,8 +1418,8 @@ impl<'a, 'b> Display for DisplayValue<'a> {
                     .unwrap();
                     write!(f, "{}", buf.trim_start_matches(' '))?;
                 }
-                if let Some(label) = label {
-                    if show_value {
+                if let Some(label) = self.show_label {
+                    if self.show_value {
                         write!(f, " ")?;
                     }
                     f.write_str(label)?;
@@ -1431,7 +1428,7 @@ impl<'a, 'b> Display for DisplayValue<'a> {
             }
 
             ValueInner::String { s, .. } | ValueInner::Variable { var_name: s, .. } => {
-                match (show_value, label) {
+                match (self.show_value, self.show_label) {
                     (true, None) => write!(f, "{s}"),
                     (false, Some(label)) => write!(f, "{label}"),
                     (true, Some(label)) => write!(f, "{s} {label}"),
@@ -1576,12 +1573,32 @@ impl ValueInner {
     // Returns an object that will format this value.  Settings on `options`
     // control whether variable and value labels are included.
     pub fn display<'a>(&'a self, options: impl AsValueOptions) -> DisplayValue<'a> {
+        let options = options.as_value_options();
+        let (show_value, show_label) = if let Some(value_label) = self.value_label() {
+            interpret_show(
+                || Settings::global().show_values,
+                options.show_values,
+                self.show(),
+                value_label,
+            )
+        } else if let Some(variable_label) = self.variable_label() {
+            interpret_show(
+                || Settings::global().show_variables,
+                options.show_variables,
+                self.show(),
+                variable_label,
+            )
+        } else {
+            (true, None)
+        };
         DisplayValue {
             inner: &self,
             markup: false,
             subscripts: &[],
             footnotes: &[],
-            options: options.as_value_options(),
+            options,
+            show_value,
+            show_label,
         }
     }
 }
index 9f788bd07da695ededdc4eadc4153ea5b72fbed0..048e045019313b63ffb1e3e67e5b685cf7fb756f 100644 (file)
@@ -267,14 +267,34 @@ impl TextDriver {
         }
     }
 
-    fn layout_cell(&self, cell: &DrawCell, mut text: &str, bb: Rect2) -> Coord2 {
+    fn cell_to_text(cell: &DrawCell) -> String {
+        cell.inner
+            .display(() /* XXX */)
+            .with_font_style(&cell.style.font_style)
+            .with_subscripts(cell.subscripts)
+            .with_footnotes(cell.footnotes)
+            .to_string()
+    }
+
+    fn layout_cell(&self, cell: &DrawCell, mut text: &str, bb: Rect2, clip: Rect2) -> Coord2 {
         if text.is_empty() {
             return Coord2::default();
         }
 
-        let mut breaks = new_line_breaks(text, bb[Axis2::X].len());
+        use Axis2::*;
+        let mut breaks = new_line_breaks(text, bb[X].len());
         let mut size = Coord2::new(0, 0);
-        for text in breaks {
+        for (text, y) in breaks.zip(bb[Y].clone()) {
+            let width = text.width();
+            if width > size[X] {
+                size[X] = width;
+            }
+            size[Y] += 1;
+
+            if !clip[Y].contains(&y) {
+                continue;
+            }
+
             
         }
         todo!()
@@ -433,20 +453,31 @@ impl Device for TextDriver {
     }
 
     fn measure_cell_width(&self, cell: &DrawCell) -> [usize; 2] {
-        let text = cell
-            .inner
-            .display(() /* XXX */)
-            .with_font_style(&cell.style.font_style)
-            .with_subscripts(cell.subscripts)
-            .with_footnotes(cell.footnotes)
-            .to_string();
-        let max_width = self.layout_cell(cell, &text, Rect2::new(0..usize::MAX, 0..usize::MAX));
-        let min_width = self.layout_cell(cell, &text, Rect2::new(0..1, 0..usize::MAX));
+        let text = Self::cell_to_text(cell);
+        let max_width = self.layout_cell(
+            cell,
+            &text,
+            Rect2::new(0..usize::MAX, 0..usize::MAX),
+            Rect2::default(),
+        );
+        let min_width = self.layout_cell(
+            cell,
+            &text,
+            Rect2::new(0..1, 0..usize::MAX),
+            Rect2::default(),
+        );
         [min_width.x(), max_width.x()]
     }
 
     fn measure_cell_height(&self, cell: &DrawCell, width: usize) -> usize {
-        todo!()
+        let text = Self::cell_to_text(cell);
+        self.layout_cell(
+            cell,
+            &text,
+            Rect2::new(0..width, 0..usize::MAX),
+            Rect2::default(),
+        )
+        .y()
     }
 
     fn adjust_break(&self, cell: &Content, size: Coord2) -> usize {