more progress on ascii output driver
authorBen Pfaff <blp@cs.stanford.edu>
Thu, 6 Mar 2025 23:42:07 +0000 (15:42 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Thu, 6 Mar 2025 23:42:07 +0000 (15:42 -0800)
rust/pspp/src/output/pivot/mod.rs
rust/pspp/src/output/pivot/output.rs
rust/pspp/src/output/render.rs
rust/pspp/src/output/table.rs
rust/pspp/src/output/text.rs
rust/pspp/src/output/text_line.rs

index 971d9392d4a055a94e1d889d04363ef78a882026..0ffa7baaeecfcebe883474e670460abf7fcd659c 100644 (file)
@@ -635,7 +635,7 @@ pub enum HorzAlign {
 }
 
 impl HorzAlign {
-    fn for_mixed(var_type: VarType) -> Self {
+    pub fn for_mixed(var_type: VarType) -> Self {
         match var_type {
             VarType::Numeric => Self::Right,
             VarType::String => Self::Left,
@@ -941,6 +941,12 @@ impl AsValueOptions for &PivotTable {
     }
 }
 
+impl AsValueOptions for &ValueOptions {
+    fn as_value_options(self) -> ValueOptions {
+        *self
+    }
+}
+
 impl AsValueOptions for ValueOptions {
     fn as_value_options(self) -> ValueOptions {
         self
@@ -1265,7 +1271,7 @@ impl<'a> DisplayValue<'a> {
         self.options.small
     }
 
-    fn var_type(&self) -> VarType {
+    pub fn var_type(&self) -> VarType {
         match self.inner {
             ValueInner::Number { .. } if self.show_label.is_none() => VarType::Numeric,
             _ => VarType::String,
index eadf61c06c0ec337a977621f8dccc51743d16290..6f8bb82ed68c57d68b870413a5c3ced040f8fa21 100644 (file)
@@ -6,8 +6,9 @@ use smallvec::{SmallVec, ToSmallVec};
 use crate::output::table::{CellInner, Table};
 
 use super::{
-    Area, Axis, Axis2, Axis3, Border, BorderStyle, BoxBorder, Category, CategoryTrait, Color,
-    Coord2, Dimension, Footnote, PivotTable, Rect2, RowColBorder, Stroke, Value,
+    Area, AsValueOptions, Axis, Axis2, Axis3, Border, BorderStyle, BoxBorder, Category,
+    CategoryTrait, Color, Coord2, Dimension, Footnote, PivotTable, Rect2, RowColBorder, Stroke,
+    Value,
 };
 
 /// All of the combinations of dimensions along an axis.
@@ -125,6 +126,7 @@ impl PivotTable {
             Coord2::new(0, 0),
             self.look.areas.clone(),
             self.borders(false),
+            self.as_value_options(),
         );
         for (y, row) in rows.enumerate() {
             table.put(
@@ -161,7 +163,13 @@ impl PivotTable {
             self.axes[Axis3::X].label_depth,
         );
         let n = EnumMap::from_fn(|axis| data[axis] + stub[axis]).into();
-        let mut body = Table::new(n, stub, self.look.areas.clone(), self.borders(printing));
+        let mut body = Table::new(
+            n,
+            stub,
+            self.look.areas.clone(),
+            self.borders(printing),
+            self.as_value_options(),
+        );
         compose_headings(
             &mut body,
             &self.axes[Axis3::X],
index 0e19ccef08ea7a84dacf4725c5149e05dddc894c..a17cd278e469807aab3d4786eb56f0e54dcfc1a7 100644 (file)
@@ -13,6 +13,7 @@ use crate::output::pivot::VertAlign;
 
 use super::pivot::{
     AreaStyle, Axis2, BorderStyle, Coord2, Footnote, Look, PivotTable, Rect2, Stroke, ValueInner,
+    ValueOptions,
 };
 use super::table::{CellInner, Content, Table};
 
@@ -149,6 +150,7 @@ pub struct DrawCell<'a> {
     pub style: &'a AreaStyle,
     pub subscripts: &'a [String],
     pub footnotes: &'a [Arc<Footnote>],
+    pub value_options: &'a ValueOptions,
 }
 
 impl<'a> DrawCell<'a> {
@@ -168,6 +170,7 @@ impl<'a> DrawCell<'a> {
             style,
             subscripts,
             footnotes,
+            value_options: &table.value_options,
         }
     }
 }
index e58e35e6934540fcdc2aa032d5c3e929ca279c2c..6684980ccd89f93f602dc09279b4aadea190023f 100644 (file)
@@ -16,7 +16,9 @@ use enum_map::{enum_map, EnumMap};
 
 use crate::output::pivot::Coord2;
 
-use super::pivot::{Area, AreaStyle, Axis2, Border, BorderStyle, HeadingRegion, Rect2, Value};
+use super::pivot::{
+    Area, AreaStyle, Axis2, Border, BorderStyle, HeadingRegion, Rect2, Value, ValueOptions,
+};
 
 #[derive(Clone)]
 pub struct CellRef<'a> {
@@ -180,6 +182,9 @@ pub struct Table {
 
     /// Horizontal and vertical rules.
     pub rules: EnumMap<Axis2, Vec<Border>>,
+
+    /// How to present values.
+    pub value_options: ValueOptions,
 }
 
 impl Table {
@@ -188,6 +193,7 @@ impl Table {
         headers: Coord2,
         areas: EnumMap<Area, AreaStyle>,
         borders: EnumMap<Border, BorderStyle>,
+        value_options: ValueOptions,
     ) -> Self {
         Self {
             n,
@@ -199,6 +205,7 @@ impl Table {
                 Axis2::X => vec![Border::Title; (n.y() + 1) * n.x()],
                 Axis2::Y => vec![Border::Title; n.y() * (n.x() + 1)],
             },
+            value_options,
         }
     }
 
index 048e045019313b63ffb1e3e67e5b685cf7fb756f..778f49292dd2abd50dc522623de7e14320e50376 100644 (file)
@@ -12,10 +12,10 @@ use unicode_width::UnicodeWidthStr;
 
 use super::{
     driver::Driver,
-    pivot::{Axis2, BorderStyle, Coord2, PivotTable, Rect2, Stroke},
+    pivot::{Axis2, BorderStyle, Coord2, DisplayValue, HorzAlign, PivotTable, Rect2, Stroke},
     render::{Device, DrawCell, Pager, Params},
     table::Content,
-    text_line::TextLine,
+    text_line::{clip_text, TextLine},
     Details, Item,
 };
 
@@ -267,13 +267,12 @@ impl TextDriver {
         }
     }
 
-    fn cell_to_text(cell: &DrawCell) -> String {
+    fn display_cell<'a>(cell: &DrawCell<'a>) -> DisplayValue<'a> {
         cell.inner
-            .display(() /* XXX */)
+            .display(cell.value_options)
             .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 {
@@ -284,20 +283,20 @@ impl TextDriver {
         use Axis2::*;
         let mut breaks = new_line_breaks(text, bb[X].len());
         let mut size = Coord2::new(0, 0);
-        for (text, y) in breaks.zip(bb[Y].clone()) {
+        for (text, _y) in breaks.zip(bb[Y].clone()) {
             let width = text.width();
             if width > size[X] {
                 size[X] = width;
             }
             size[Y] += 1;
+        }
+        size
+    }
 
-            if !clip[Y].contains(&y) {
-                continue;
-            }
-
-            
+    fn reserve(&mut self, y: usize) {
+        if y >= self.lines.len() {
+            self.lines.resize(y + 1, TextLine::new());
         }
-        todo!()
     }
 }
 
@@ -438,9 +437,9 @@ impl Driver for TextDriver {
         match &item.details {
             Details::Chart => todo!(),
             Details::Image => todo!(),
-            Details::Group(vec) => todo!(),
+            Details::Group(_) => todo!(),
             Details::Message(diagnostic) => todo!(),
-            Details::PageBreak => todo!(),
+            Details::PageBreak => (),
             Details::Table(pivot_table) => self.output_table(pivot_table),
             Details::Text(text) => todo!(),
         }
@@ -453,7 +452,7 @@ impl Device for TextDriver {
     }
 
     fn measure_cell_width(&self, cell: &DrawCell) -> [usize; 2] {
-        let text = Self::cell_to_text(cell);
+        let text = Self::display_cell(cell).to_string();
         let max_width = self.layout_cell(
             cell,
             &text,
@@ -470,7 +469,7 @@ impl Device for TextDriver {
     }
 
     fn measure_cell_height(&self, cell: &DrawCell, width: usize) -> usize {
-        let text = Self::cell_to_text(cell);
+        let text = Self::display_cell(cell).to_string();
         self.layout_cell(
             cell,
             &text,
@@ -490,14 +489,41 @@ impl Device for TextDriver {
 
     fn draw_cell(
         &mut self,
-        draw_cell: &DrawCell,
-        alternate_row: bool,
+        cell: &DrawCell,
+        _alternate_row: bool,
         bb: &Rect2,
         valign_offset: usize,
         spill: EnumMap<Axis2, [usize; 2]>,
         clip: &Rect2,
     ) {
-        todo!()
+        let display = Self::display_cell(cell);
+        let text = display.to_string();
+        let horz_align = cell
+            .style
+            .cell_style
+            .horz_align
+            .unwrap_or_else(|| HorzAlign::for_mixed(display.var_type()));
+
+        use Axis2::*;
+        let mut breaks = new_line_breaks(&text, bb[X].len());
+        for (text, y) in breaks.zip(bb[Y].start + valign_offset..bb[Y].end) {
+            let width = text.width();
+            if !clip[Y].contains(&y) {
+                continue;
+            }
+
+            let x = match horz_align {
+                HorzAlign::Right | HorzAlign::Decimal { .. } => bb[X].end - width,
+                HorzAlign::Left => bb[X].start,
+                HorzAlign::Center => (bb[X].start + bb[X].end - width + 1) / 2,
+            };
+            let Some((x, text)) = clip_text(&text, &(x..x + width), &clip[X]) else {
+                continue;
+            };
+
+            self.reserve(y);
+            self.lines[y].put(x, text);
+        }
     }
 
     fn scale(&mut self, factor: f64) {
index 54c5f3a7814e092cf02affde85d1ba166c4c8de4..21e9e9a395399afc546886d0e9d19a5a5b013e9e 100644 (file)
@@ -8,7 +8,7 @@ use unicode_width::UnicodeWidthChar;
 ///
 /// Designed to make appending text fast, and access and modification of other
 /// column positions possible.
-#[derive(Default)]
+#[derive(Clone, Default)]
 pub struct TextLine {
     /// Content.
     string: String,
@@ -527,3 +527,34 @@ mod test {
         }
     }
 }
+
+pub fn clip_text<'a>(
+    text: &'a str,
+    bb: &Range<usize>,
+    clip: &Range<usize>,
+) -> Option<(usize, &'a str)> {
+    let mut x = bb.start;
+    let mut width = bb.len();
+
+    let mut iter = text.chars();
+    while x < clip.start {
+        let c = iter.next()?;
+        if let Some(w) = c.width() {
+            x += w;
+            width = width.checked_sub(w)?;
+        }
+    }
+    if x + width > clip.end {
+        if x >= clip.end {
+            return None;
+        }
+
+        while x + width > clip.end {
+            let c = iter.next_back()?;
+            if let Some(w) = c.width() {
+                width = width.checked_sub(w)?;
+            }
+        }
+    }
+    Some((x, iter.as_str()))
+}