work
authorBen Pfaff <blp@cs.stanford.edu>
Mon, 1 Dec 2025 01:38:20 +0000 (17:38 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Mon, 1 Dec 2025 01:38:20 +0000 (17:38 -0800)
rust/pspp/src/output/drivers/cairo/fsm.rs
rust/pspp/src/output/drivers/text.rs
rust/pspp/src/output/render.rs
rust/pspp/src/output/spv.rs

index 41e8d404800454be25dbe8108c038c0a12ca0984..b75479b7ae0d6c2cc8bd779446ac15ceee271a32 100644 (file)
@@ -160,10 +160,7 @@ impl CairoFsm {
         context.save().unwrap();
         let used = match &self.item.details {
             Details::Table(_) => self.draw_table(context, space),
-            _ => {
-                eprintln!("{}", &self.item);
-                0
-            }
+            _ => 0,
         };
         context.restore().unwrap();
 
index 181a6c2ddbbf15ee47c399ea5ab0ca8d5e36b089..65757b220f04163f54bb0c66e9fb728504737fd0 100644 (file)
@@ -595,7 +595,7 @@ impl Device for TextRenderer {
         use Axis2::*;
         let x = bb[X].start.max(0)..bb[X].end.min(self.width);
         let y = bb[Y].start.max(0)..bb[Y].end;
-        if x.is_empty() || x.end >= self.width {
+        if x.is_empty() || x.start >= self.width {
             return;
         }
 
index e8f900d97140f9b67c20e71967c374ff3922c8c7..a37618b83e49582ba2ef7c44ad58af70344eb2ae 100644 (file)
@@ -16,7 +16,7 @@
 
 use std::cmp::{max, min};
 use std::collections::HashMap;
-use std::iter::once;
+use std::iter::{once, zip};
 use std::ops::Range;
 use std::sync::Arc;
 
@@ -200,35 +200,37 @@ struct Page {
 
     /// "Cell positions".
     ///
-    /// cp[X] represents x positions within the table.
-    /// cp[X][0] = 0.
-    /// cp[X][1] = the width of the leftmost vertical rule.
-    /// cp[X][2] = cp[X][1] + the width of the leftmost column.
-    /// cp[X][3] = cp[X][2] + the width of the second-from-left vertical rule.
-    /// and so on:
-    /// cp[X][2 * n[X]] = x position of the rightmost vertical rule.
-    /// cp[X][2 * n[X] + 1] = total table width including all rules.
+    /// `cp[X]` represents `x` positions within the table:
     ///
-    /// Similarly, cp[Y] represents y positions within the table.
-    /// cp[Y][0] = 0.
-    /// cp[Y][1] = the height of the topmost horizontal rule.
-    /// cp[Y][2] = cp[Y][1] + the height of the topmost row.
-    /// cp[Y][3] = cp[Y][2] + the height of the second-from-top horizontal rule.
-    /// and so on:
-    /// cp[Y][2 * n[Y]] = y position of the bottommost horizontal rule.
-    /// cp[Y][2 * n[Y] + 1] = total table height including all rules.
+    /// - `cp[X][0]` = 0.
+    /// - `cp[X][1]` = the width of the leftmost vertical rule.
+    /// - `cp[X][2]` = `cp[X][1]` + the width of the leftmost column.
+    /// - `cp[X][3]` = `cp[X][2]` + the width of the second-from-left vertical rule.
+    /// - ...
+    /// - `cp[X][2 * n[X]]` = `x` position of the rightmost vertical rule.
+    /// - `cp[X][2 * n[X] + 1]` = total table width including all rules.
+    ///
+    /// Similarly, `cp[Y]` represents `y` positions within the table:
+    ///
+    /// - `cp[Y][0]` = 0.
+    /// - `cp[Y][1]` = the height of the topmost horizontal rule.
+    /// - `cp[Y][2]` = `cp[Y][1]` + the height of the topmost row.
+    /// - `cp[Y][3]` = `cp[Y][2]` + the height of the second-from-top horizontal rule.
+    /// - ...
+    /// - `cp[Y][2 * n[Y]]` = `y` position of the bottommost horizontal rule.
+    /// - `cp[Y][2 * n[Y] + 1]` = total table height including all rules.
     ///
     /// Rules and columns can have width or height 0, in which case consecutive
     /// values in this array are equal.
     cp: EnumMap<Axis2, Vec<usize>>,
 
-    /// [Break::next] can break a table such that some cells are not fully
-    /// contained within a render_page.  This will happen if a cell is too wide
-    /// or two tall to fit on a single page, or if a cell spans multiple rows
-    /// or columns and the page only includes some of those rows or columns.
+    /// [Break::next] can break a [Page] in the middle of a cell, if a cell is
+    /// too wide or two tall to fit on a single page, or if a cell spans
+    /// multiple rows or columns and the page only includes some of those rows
+    /// or columns.
     ///
-    /// This hash table contains represents each such cell that doesn't
-    /// completely fit on this page.
+    /// This hash table represents each such cell that doesn't completely fit on
+    /// this page.
     ///
     /// Each overflow cell borders at least one header edge of the table and may
     /// border more.  (A single table cell that is so large that it fills the
@@ -236,10 +238,12 @@ struct Page {
     ///
     /// # Interpretation
     ///
-    /// overflow[X][0]: space trimmed off its left side.
-    /// overflow[X][1]: space trimmed off its right side.
-    /// overflow[Y][0]: space trimmed off its top.
-    /// overflow[Y][1]: space trimmed off its bottom.
+    /// Given `overflow` as a value in the [HashMap]:
+    ///
+    /// - `overflow[Axis2::X][0]`: space trimmed off the cell's left side.
+    /// - `overflow[Axis2::X][1]`: space trimmed off the cell's right side.
+    /// - `overflow[Axis2::Y][0]`: space trimmed off the cell's top.
+    /// - `overflow[Axis2::Y][1]`: space trimmed off the cell's bottom.
     ///
     /// During rendering, this information is used to position the rendered
     /// portion of the cell within the available space.
@@ -249,52 +253,51 @@ struct Page {
     /// still included in overflow values.
     ///
     /// Suppose, for example, that a cell that joins 2 columns has a width of 60
-    /// pixels and content "abcdef", that the 2 columns that it joins have
+    /// pixels and content `abcdef`, that the 2 columns that it joins have
     /// widths of 20 and 30 pixels, respectively, and that therefore the rule
     /// between the two joined columns has a width of 10 (20 + 10 + 30 = 60).
     /// It might render like this, if each character is 10x10, and showing a few
     /// extra table cells for context:
     ///
     /// ```text
-    /// +------+
-    /// |abcdef|
-    /// +--+---+
-    /// |gh|ijk|
-    /// +--+---+
+    /// ┌──────┐
+    /// │abcdef│
+    /// ├──┬───┤
+    /// │gh│ijk│
+    /// └──┴───┘
     /// ```
     ///
-    /// If this render_page is broken at the rule that separates "gh" from
-    /// "ijk", then the page that contains the left side of the "abcdef" cell
-    /// will have overflow[X][1] of 10 + 30 = 40 for its portion of the cell,
+    /// If this [Page] is broken at the rule that separates `gh` from
+    /// `ijk`, then the page that contains the left side of the `abcdef` cell
+    /// will have `overflow[Axis2::X][1]` of 10 + 30 = 40 for its portion of the cell,
     /// and the page that contains the right side of the cell will have
-    /// overflow[X][0] of 20 + 10 = 30.  The two resulting pages would look like
+    /// `overflow[Axis2::X][0]` of 20 + 10 = 30.  The two resulting pages would look like
     /// this:
     ///
     /// ```text
-    /// +---
-    /// |abc
-    /// +--+
-    /// |gh|
-    /// +--+
+    /// ┌───
+    /// abc
+    /// ├──┬
+    /// │gh│
+    /// └──┴
     /// ```
     ///
     /// and:
     ///
     /// ```text
-    /// ----+
-    /// cdef|
-    /// +---+
-    /// |ijk|
-    /// +---+
+    /// ────┐
+    /// cdef
+    /// ┬───┤
+    /// │ijk│
+    /// ┴───┘
     /// ```
     /// Each entry maps from a cell that overflows to the space that has been
-    /// trimmed off the cell:
+    /// trimmed off the cell.
     overflows: HashMap<Coord2, EnumMap<Axis2, [usize; 2]>>,
 
     /// If a single column (or row) is too wide (or tall) to fit on a page
-    /// reasonably, then render_break_next() will split a single row or column
-    /// across multiple render_pages.  This member indicates when this has
-    /// happened:
+    /// reasonably, then [Break::next] will split a single row or column across
+    /// multiple [Page]s.  This member indicates when this has happened:
     ///
     /// is_edge_cutoff[X][0] is true if pixels have been cut off the left side
     /// of the leftmost column in this page, and false otherwise.
@@ -458,15 +461,12 @@ impl Page {
         let cp_x = if table_widths[Max] <= device.params().size[X] {
             // Fits even with maximum widths.  Use them.
             Self::use_row_widths(&columns[Max], &rules[X])
-        } else if table_widths[Min] <= device.params().size[X] {
+        } else if device.params().size[X] > table_widths[Min] {
             // Fits with minimum widths, so distribute the leftover space.
-            //Self::new_with_interpolated_widths()
-            Self::interpolate_row_widths(
-                device.params(),
-                &columns[Min],
-                &columns[Max],
-                table_widths[Min],
-                table_widths[Max],
+            Self::interpolate_column_widths(
+                device.params().size[Axis2::X],
+                &columns,
+                &table_widths,
                 &rules[X],
             )
         } else {
@@ -543,21 +543,18 @@ impl Page {
         vec
     }
 
-    fn interpolate_row_widths(
-        params: &Params,
-        rows_min: &[usize],
-        rows_max: &[usize],
-        w_min: usize,
-        w_max: usize,
+    fn interpolate_column_widths(
+        target: usize,
+        columns: &EnumMap<Extreme, Vec<usize>>,
+        widths: &EnumMap<Extreme, usize>,
         rules: &[usize],
     ) -> Vec<usize> {
-        let avail = params.size[Axis2::X] - w_min;
-        let wanted = w_max - w_min;
+        use Extreme::*;
+
+        let avail = target - widths[Min];
+        let wanted = widths[Max] - widths[Min];
         let mut w = wanted / 2;
-        let rows_mid = rows_min
-            .iter()
-            .copied()
-            .zip(rows_max.iter().copied())
+        let rows_mid = zip(columns[Min].iter().copied(), columns[Max].iter().copied())
             .map(|(min, max)| {
                 w += avail * (max - min);
                 let extra = w / wanted;
index d87c608669111a20791faf603081ada667f8a41c..7be381b96b45485d731d7eadd632f22d148adfcc 100644 (file)
@@ -285,7 +285,7 @@ impl Heading {
                         ContainerContent::Tree => new_error_item("trees not yet implemented")
                             .with_spv_info(SpvInfo::new(structure_member).with_error()),
                     };
-                    items.push(item);
+                    items.push(item.with_show(container.visibility == Visibility::Visible));
                 }
                 HeadingContent::Heading(mut heading) => {
                     let show = !heading.visibility.is_some();
@@ -525,7 +525,7 @@ enum PageBreakBefore {
     Inherit,
 }
 
-#[derive(Deserialize, Debug, Default)]
+#[derive(Deserialize, Debug, Default, PartialEq, Eq)]
 #[serde(rename_all = "camelCase")]
 enum Visibility {
     #[default]