some basic table rendering basically works
authorBen Pfaff <blp@cs.stanford.edu>
Sat, 5 Apr 2025 17:33:54 +0000 (10:33 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Sat, 5 Apr 2025 17:33:54 +0000 (10:33 -0700)
rust/Cargo.lock
rust/pspp/Cargo.toml
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 62a1ba22478b039c250a1aceb752ae141b6a107b..1a6e0fa4c472e395c566503429dd6b6161f6be39 100644 (file)
@@ -334,6 +334,27 @@ dependencies = [
  "parking_lot_core",
 ]
 
+[[package]]
+name = "derive_more"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+ "unicode-xid",
+]
+
 [[package]]
 name = "diff"
 version = "0.1.13"
@@ -1012,6 +1033,7 @@ dependencies = [
  "chrono",
  "clap",
  "color",
+ "derive_more",
  "diff",
  "either",
  "encoding_rs",
@@ -1545,6 +1567,12 @@ version = "0.1.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
 
+[[package]]
+name = "unicode-xid"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+
 [[package]]
 name = "url"
 version = "2.5.2"
index 9fc4a923f03f9474776212f268ebc055d8e7ce02..07f6d4ebf36059d8194678f66e7a1d207d4ba1fa 100644 (file)
@@ -41,6 +41,7 @@ serde = { version = "1.0.218", features = ["derive"] }
 color = { version = "0.2.3", features = ["serde"] }
 binrw = "0.14.1"
 ndarray = "0.16.1"
+derive_more = { version = "2.0.1", features = ["debug"] }
 
 [target.'cfg(windows)'.dependencies]
 windows-sys = { version = "0.48.0", features = ["Win32_Globalization"] }
index f5350aa1b9c211f1ffb8da8b1552142824424be2..ec4b5ce22afa46b65e2395468f875c1d249e6fc8 100644 (file)
@@ -1713,7 +1713,7 @@ impl Display for Display26Adic {
 ///
 /// 5. A template. PSPP doesn't create these itself yet, but it can read and
 ///    interpret those created by SPSS.
-#[derive(Clone, Debug, Default)]
+#[derive(Clone, Default)]
 pub struct Value {
     pub inner: ValueInner,
     pub styling: Option<Box<ValueStyle>>,
@@ -2017,6 +2017,12 @@ impl Value {
     }
 }
 
+impl Debug for Value {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{:?}", self.display(()).to_string())
+    }
+}
+
 #[derive(Clone, Debug, Default)]
 pub enum ValueInner {
     Number {
index 868bb81df8fa996f4a49b39b934ae7c73d7c2111..9f1cdbb808bae4ab458a3ef90862345ce48ad13b 100644 (file)
@@ -305,11 +305,11 @@ impl PivotTable {
             self.output_footnotes(&self.collect_footnotes(tables.into_iter().flatten()));
 
         OutputTables {
-            title,
-            layers,
+            title: None,
+            layers: None,
             body,
-            caption,
-            footnotes,
+            caption: None,
+            footnotes: None,
         }
     }
 
index c02a385dffcbe74a98d6b103f9f0af095b1df8ad..b2e7fa5c8623d928c2f702d9a816871e5fb929ff 100644 (file)
@@ -373,15 +373,19 @@ impl Page {
     fn new(table: Arc<Table>, device: &dyn Device, min_width: usize, look: &Look) -> Self {
         use Axis2::*;
 
-        println!("{table:#?}");
+        dbg!(&table);
         let n = table.n.clone();
 
         // Figure out rule widths.
+        //
+        // `rules[X]` is vertical rules.
+        // `rules[Y]` is horizontal rules.
         let rules = EnumMap::from_fn(|axis| {
             (0..=n[axis])
                 .map(|z| measure_rule(&*device, &*table, axis, z))
                 .collect::<Vec<_>>()
         });
+        dbg!(&rules);
 
         let px_size = device.params().px_size.unwrap_or_default();
         let heading_widths = look
@@ -418,6 +422,7 @@ impl Page {
                 }
             }
         }
+        dbg!(&unspanned_columns);
 
         // Distribute widths of spanned columns.
         let mut columns = unspanned_columns.clone();
@@ -453,6 +458,7 @@ impl Page {
                 columns[1][i] = columns[0][i];
             }
         }
+        dbg!(&columns);
 
         // Decide final column widths.
         let rule_widths = rules[X].iter().copied().sum::<usize>();
@@ -460,6 +466,7 @@ impl Page {
             .iter()
             .map(|row| row.iter().sum::<usize>() + rule_widths)
             .collect::<SmallVec<[usize; 2]>>();
+        dbg!(&table_widths);
 
         let cp_x = if table_widths[1] <= device.params().size[X] {
             // Fits even with maximum widths.  Use them.
@@ -480,6 +487,7 @@ impl Page {
             // later we can break it horizontally into multiple pages.
             Self::use_row_widths(&columns[0], &rules[X])
         };
+        dbg!(&cp_x);
 
         // Calculate heights of cells that do not span multiple rows.
         let mut unspanned_rows = vec![0; n[Y]];
@@ -539,17 +547,16 @@ impl Page {
         }
     }
 
-    fn accumulate_vec(mut vec: Vec<usize>) -> Vec<usize> {
+    fn use_row_widths(rows: &[usize], rules: &[usize]) -> Vec<usize> {
+        let mut vec = once(0)
+            .chain(interleave(rules, rows).copied())
+            .collect::<Vec<_>>();
         for i in 1..vec.len() {
             vec[i] += vec[i - 1]
         }
         vec
     }
 
-    fn use_row_widths(rows: &[usize], rules: &[usize]) -> Vec<usize> {
-        once(0).chain(interleave(rules, rows).copied()).collect()
-    }
-
     fn interpolate_row_widths(
         params: &Params,
         rows_min: &[usize],
@@ -854,7 +861,7 @@ impl Page {
             let mut x = cells[X].start;
             while x < cells[X].end {
                 if !is_rule(x) && !is_rule(y) {
-                    let cell = self.get_cell(Coord2::new(x, y));
+                    let cell = self.get_cell(Coord2::new(x / 2, y / 2));
                     self.draw_cell(device, ofs, &cell);
                     x = rule_ofs(cell.rect[X].end);
                 } else {
@@ -865,7 +872,7 @@ impl Page {
 
         for y in cells[Y].clone() {
             for x in cells[X].clone() {
-                if is_rule(x) && is_rule(y) {
+                if is_rule(x) || is_rule(y) {
                     self.draw_rule(device, ofs, Coord2::new(x, y));
                 }
             }
@@ -940,12 +947,14 @@ impl Page {
             self.cp[a][cell.rect[a].start * 2 + 1]..self.cp[a][cell.rect[a].end * 2]
         })
         .translate(ofs);
-        let spill = EnumMap::from_fn(|a| {
-            [
-                self.rule_width(a, cell.rect[a].start) / 2,
-                self.rule_width(a, cell.rect[a].end) / 2,
-            ]
-        });
+        /*
+            let spill = EnumMap::from_fn(|a| {
+                [
+                    self.rule_width(a, cell.rect[a].start) / 2,
+                    self.rule_width(a, cell.rect[a].end) / 2,
+                ]
+        });*/
+        let spill = EnumMap::from_fn(|_| [0, 0]);
 
         let clip = if let Some(overflow) = self.overflows.get(&cell.rect.top_left()) {
             Rect2::from_fn(|a| {
@@ -1065,16 +1074,15 @@ fn distribute_spanned_width(
     spanned: &mut [usize],
     rules: &[usize],
 ) {
-    println!("{unspanned:?}");
-    println!("{spanned:?}");
-    println!("{rules:?}");
     let n = unspanned.len();
     debug_assert!(n >= 1);
     debug_assert_eq!(spanned.len(), n);
     debug_assert_eq!(rules.len(), n + 1);
 
-    let total_unspanned =
-        unspanned.iter().sum::<usize>() + (&rules[1..n - 1]).iter().copied().sum::<usize>();
+    let total_unspanned = unspanned.iter().sum::<usize>()
+        + rules
+            .get(1..n - 1)
+            .map_or(0, |rules| rules.iter().copied().sum::<usize>());
     if total_unspanned >= width {
         return;
     }
@@ -1319,7 +1327,7 @@ impl Break {
     }
 
     fn find_breakpoint(&mut self, device: &dyn Device, size: usize) -> Option<(usize, usize)> {
-        println!("{:?} {:?}", self.axis, self.z..self.page.n[self.axis]);
+        dbg!(self.axis, self.z..self.page.n[self.axis]);
         for z in self.z..self.page.n[self.axis] {
             let needed = self.needed_size(z + 1);
             if needed > size {
@@ -1366,8 +1374,6 @@ impl Pager {
             layer_indexes.unwrap_or(&pivot_table.current_layer),
             device.params().printing,
         );
-        println!("{:#?}", pivot_table);
-        println!("{:#?}", output.body);
 
         // Figure out the width of the body of the table. Use this to determine
         // the base scale.
@@ -1432,6 +1438,7 @@ impl Pager {
 
     /// True if there's content left to rnder.
     pub fn has_next(&mut self, device: &dyn Device) -> bool {
+        return !self.pages.is_empty();
         while self
             .y_break
             .as_mut()
@@ -1465,6 +1472,10 @@ impl Pager {
     pub fn draw_next(&mut self, device: &mut dyn Device, mut space: usize) -> usize {
         use Axis2::*;
 
+        let page = self.pages.pop().unwrap();
+        page.draw(device, Coord2::new(0, 0));
+        return page.total_size(Y);
+
         if self.scale != 1.0 {
             device.scale(self.scale);
             space = (space as f64 / self.scale) as usize;
index 6b9ef5f66a54266b08aaf7b1b95a21cd8ff19ffa..7d6443bcdf21367c19a21ec9b0aa4ba0d927a6c7 100644 (file)
@@ -166,7 +166,7 @@ impl CellInner {
 }
 
 /// A table.
-#[derive(Debug)]
+#[derive(derive_more::Debug)]
 pub struct Table {
     /// Number of rows and columns.
     pub n: Coord2,
@@ -177,15 +177,18 @@ pub struct Table {
     pub contents: Array2<Content>,
 
     /// Styles for areas of the table.
+    #[debug(skip)]
     pub areas: EnumMap<Area, AreaStyle>,
 
     /// Styles for borders in the table.
+    #[debug(skip)]
     pub borders: EnumMap<Border, BorderStyle>,
 
-    /// Horizontal and vertical rules.
+    /// Horizontal ([Axis2::Y]) and vertical ([Axis2::X]) rules.
     pub rules: EnumMap<Axis2, Array2<Border>>,
 
     /// How to present values.
+    #[debug(skip)]
     pub value_options: ValueOptions,
 }
 
@@ -204,8 +207,8 @@ impl Table {
             areas,
             borders,
             rules: enum_map! {
-                Axis2::X => Array::from_elem((n.x(), n.y() + 1), Border::Title),
-                Axis2::Y => Array::from_elem((n.x() + 1, n.y()), Border::Title),
+                Axis2::X => Array::from_elem((n.x() + 1, n.y()), Border::Title),
+                Axis2::Y => Array::from_elem((n.x(), n.y() + 1), Border::Title),
             },
             value_options,
         }
@@ -241,7 +244,7 @@ impl Table {
         debug_assert!(x.start <= x.end);
         debug_assert!(x.end <= self.n.x());
         for x in x {
-            self.rules[Axis2::X][[x, y]] = border;
+            self.rules[Axis2::Y][[x, y]] = border;
         }
     }
 
@@ -250,7 +253,7 @@ impl Table {
         debug_assert!(y.start <= y.end);
         debug_assert!(y.end <= self.n.y());
         for y in y {
-            self.rules[Axis2::Y][[x, y]] = border;
+            self.rules[Axis2::X][[x, y]] = border;
         }
     }
 
index 5c2cbfbffb686fff6028a691fb9d3af7f1d54135..e275cd6d686cc5a4bf821ee4e40ca43d1aaecbe9 100644 (file)
@@ -267,7 +267,12 @@ impl TextDriver {
                 }
                 self.n_objects += 1;
 
-                pager.draw_next(self, usize::MAX);
+                let h = pager.draw_next(self, usize::MAX);
+
+                for (ln, line) in self.lines[..h].iter_mut().enumerate() {
+                    println!("{ln}: {}", line);
+                    line.clear();
+                }
             }
         }
     }
@@ -332,7 +337,6 @@ where
             } else {
                 postindex
             };
-            println!("index={index} {:?}", &self.text[index..]);
             if index <= self.indexes.end {
                 dbg!();
                 continue;
@@ -340,14 +344,12 @@ where
 
             let segment_width = self.text[self.indexes.end..index].width();
             if self.width == 0 || self.width + segment_width <= self.max_width {
-                dbg!();
                 // Add this segment to the current line.
                 self.width += segment_width;
                 self.indexes.end = index;
 
                 // If this was a new-line, we're done.
                 if opportunity == BreakOpportunity::Mandatory {
-                    dbg!();
                     let segment = self.text[self.indexes.clone()].trim_end_matches('\n');
                     self.indexes = postindex..postindex;
                     self.width = 0;
@@ -483,10 +485,10 @@ impl Device for TextDriver {
         }
 
         let lines = Lines {
-            l: styles[X][0].stroke.into(),
-            r: styles[X][1].stroke.into(),
-            t: styles[Y][0].stroke.into(),
-            b: styles[Y][1].stroke.into(),
+            l: styles[Y][0].stroke.into(),
+            r: styles[Y][1].stroke.into(),
+            t: styles[X][0].stroke.into(),
+            b: styles[X][1].stroke.into(),
         };
         let c = self.box_chars[lines];
         for y in y {
@@ -505,6 +507,7 @@ impl Device for TextDriver {
     ) {
         let display = Self::display_cell(cell);
         let text = display.to_string();
+        println!("text={text:?} bb={bb:?}");
         let horz_align = cell
             .style
             .cell_style
index b861da2df3823184148d3662a6cbe62bc1cf7e70..713b892b39ad06dca8f6adc80a1b0485857665c1 100644 (file)
@@ -1,5 +1,9 @@
 use enum_iterator::Sequence;
-use std::{borrow::Cow, fmt::Debug, ops::Range};
+use std::{
+    borrow::Cow,
+    fmt::{Debug, Display},
+    ops::Range,
+};
 
 use unicode_width::UnicodeWidthChar;
 
@@ -120,11 +124,17 @@ impl TextLine {
         }
     }
 
-    fn str(&self) -> &str {
+    pub fn str(&self) -> &str {
         &self.string
     }
 }
 
+impl Display for TextLine {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str(&self.string)
+    }
+}
+
 /// Position of one or more characters within a [TextLine].
 #[derive(Debug)]
 struct Position {