rendering fixes
authorBen Pfaff <blp@cs.stanford.edu>
Fri, 19 Dec 2025 01:02:10 +0000 (17:02 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Fri, 19 Dec 2025 01:02:10 +0000 (17:02 -0800)
rust/pspp/src/output.rs
rust/pspp/src/output/drivers/cairo/fsm.rs
rust/pspp/src/output/pivot/output.rs
rust/pspp/src/output/render.rs
rust/pspp/src/spv/read/css.rs
rust/pspp/src/spv/read/html.rs
rust/pspp/src/spv/read/light.rs

index 3133d61695e0777268da288241513cafce51613d..d0b29c3aab34ae945a56099364f0e440cfd6a42e 100644 (file)
@@ -139,7 +139,7 @@ impl Item {
     /// This always returns true for headings because their contents are always
     /// shown (although headings can be collapsed in an outline view).
     pub fn is_shown(&self) -> bool {
-        self.details.kind() == ItemKind::Heading || self.show
+        self.details.is_heading() || self.show
     }
 }
 
@@ -556,21 +556,30 @@ impl ItemCursor {
     }
 
     pub fn next(&mut self) {
-        let Some(cur) = self.cur.take() else {
-            return;
-        };
-        if let Some(first_child) = cur.details.children().first() {
-            self.cur = Some(first_child.clone());
-            self.stack.push((cur, 1));
-        } else {
-            while let Some((item, index)) = self.stack.pop() {
-                if let Some(child) = item.details.children().get(index) {
-                    self.cur = Some(child.clone());
-                    self.stack.push((item, index + 1));
-                    return;
+        fn inner(this: &mut ItemCursor) {
+            let Some(cur) = this.cur.take() else {
+                return;
+            };
+            if let Some(first_child) = cur.details.children().first() {
+                this.cur = Some(first_child.clone());
+                this.stack.push((cur, 1));
+            } else {
+                while let Some((item, index)) = this.stack.pop() {
+                    if let Some(child) = item.details.children().get(index) {
+                        this.cur = Some(child.clone());
+                        this.stack.push((item, index + 1));
+                        return;
+                    }
                 }
             }
         }
+
+        inner(self);
+        while let Some(cur) = &self.cur
+            && !cur.is_shown()
+        {
+            inner(self);
+        }
     }
 
     // Returns the label for the heading with the given `level` in the stack
index 88def4fcdf688113349213996bf56ed1a4147b3b..165e304c1170d296486647202b016df26d9c7f5f 100644 (file)
@@ -31,7 +31,7 @@ use crate::{
         Details, Item,
         drivers::cairo::{px_to_xr, xr_to_pt},
         pivot::{
-            Axis2, Coord2, Rect2,
+            Axis2, Coord2, PivotTable, Rect2,
             look::{BorderStyle, Color, FontStyle, HorzAlign, Stroke},
         },
         render::{Device, Extreme, Pager, Params},
@@ -136,6 +136,11 @@ impl CairoFsm {
             params: &params,
             context,
         };
+        let item = if let Some(text) = item.details.as_text() {
+            Arc::new(Item::new(PivotTable::from(dbg!(text).clone())))
+        } else {
+            item
+        };
         let (layer_iterator, pager) = match &item.details {
             Details::Table(pivot_table) => {
                 let mut layer_iterator = pivot_table.layers(printing);
index 6b36ececc117eae0727e65e0123a0ce5c0b83b45..00e1e714577e59fb32627b10ff0f0248b6cdbeac 100644 (file)
@@ -170,6 +170,27 @@ impl PivotTable {
     }
 
     fn borders(&self, printing: bool) -> EnumMap<Border, BorderStyle> {
+        fn resolve_border_style(
+            border: Border,
+            borders: &EnumMap<Border, BorderStyle>,
+            show_grid_lines: bool,
+        ) -> BorderStyle {
+            let style = borders[border];
+            if style.stroke != Stroke::None {
+                style
+            } else {
+                let style = borders[border.fallback()];
+                if style.stroke != Stroke::None || !show_grid_lines {
+                    style
+                } else {
+                    BorderStyle {
+                        stroke: Stroke::Dashed,
+                        color: Color::BLACK,
+                    }
+                }
+            }
+        }
+
         EnumMap::from_fn(|border| {
             resolve_border_style(
                 border,
@@ -694,24 +715,3 @@ impl<'a> Headings<'a> {
         }
     }
 }
-
-fn resolve_border_style(
-    border: Border,
-    borders: &EnumMap<Border, BorderStyle>,
-    show_grid_lines: bool,
-) -> BorderStyle {
-    let style = borders[border];
-    if style.stroke != Stroke::None {
-        style
-    } else {
-        let style = borders[border.fallback()];
-        if style.stroke != Stroke::None || !show_grid_lines {
-            style
-        } else {
-            BorderStyle {
-                stroke: Stroke::Dashed,
-                color: Color::BLACK,
-            }
-        }
-    }
-}
index 6fb039923f5d63771890bf0dc6a0a84205fca924..be194a7dcfc7a849058503716764aeaacf62012d 100644 (file)
@@ -965,7 +965,9 @@ impl Break {
         for c in 0..self.page.table.n()[self.axis] {
             let position = cp[c * 2 + 3];
             if position > range.end {
-                if self.page.table.cell_width(self.axis, c) >= device.params().min_break[self.axis]
+                if c == 0
+                    || self.page.table.cell_width(self.axis, c)
+                        >= device.params().min_break[self.axis]
                 {
                     // XXX various way to choose a better breakpoint
                     return (range.end, range.end);
index a6c04997b4da14646ad6f98babd4dece6e26ea36..abd7712d19ccc801f25168b621b5756511db0e30 100644 (file)
@@ -137,6 +137,8 @@ impl Style {
                     "text-decoration" => Some((Style::Underline, value == "underline")),
                     "font-family" => Some((Style::Face(value.into()), true)),
                     "font-size" => value
+                        .strip_suffix("pt")
+                        .unwrap_or(&value)
                         .parse::<i32>()
                         .ok()
                         .map(|size| (Style::Size(size as f64 * 0.75), true)),
index cbed0ddc071b9165a4f929e4c76825433126f3b7..f334ecc95ae8a5201653c8383e7871aa54789d2b 100644 (file)
@@ -732,7 +732,9 @@ fn parse_nodes(nodes: &[Node]) -> Markup {
                     Markup::Text(unescape(&text).unwrap_or(Cow::from(text)).into_owned()),
                 );
             }
-            Node::Element(br) if br.name.eq_ignore_ascii_case("br") => {
+            // SPSS often starts paragraphs with an initial `<BR>` that it
+            // ignores, but it does honor `<br>`.  So weird.
+            Node::Element(br) if br.name == "br" => {
                 add_markup(&mut retval, Markup::Text('\n'.into()));
             }
             Node::Element(element) => {
index aa9194aa3e7a1a0c42ea20e8a07c972a2e904add..321255a99759dda10e7f0b9fe1c890988e48ca74 100644 (file)
@@ -574,13 +574,13 @@ impl Border {
             9 => look::Border::DataLeft,
             10 => look::Border::DataLeft,
             11 => look::Border::Dimension(RowColBorder(HeadingRegion::Rows, Axis2::X)),
-            12 => look::Border::Dimension(RowColBorder(HeadingRegion::Rows, Axis2::X)),
+            12 => look::Border::Dimension(RowColBorder(HeadingRegion::Rows, Axis2::Y)),
             13 => look::Border::Dimension(RowColBorder(HeadingRegion::Columns, Axis2::X)),
-            14 => look::Border::Dimension(RowColBorder(HeadingRegion::Columns, Axis2::X)),
+            14 => look::Border::Dimension(RowColBorder(HeadingRegion::Columns, Axis2::Y)),
             15 => look::Border::Category(RowColBorder(HeadingRegion::Rows, Axis2::X)),
-            16 => look::Border::Category(RowColBorder(HeadingRegion::Rows, Axis2::X)),
+            16 => look::Border::Category(RowColBorder(HeadingRegion::Rows, Axis2::Y)),
             17 => look::Border::Category(RowColBorder(HeadingRegion::Columns, Axis2::X)),
-            18 => look::Border::Category(RowColBorder(HeadingRegion::Columns, Axis2::X)),
+            18 => look::Border::Category(RowColBorder(HeadingRegion::Columns, Axis2::Y)),
             _ => return None,
         };