start adding more pdf tests
authorBen Pfaff <blp@cs.stanford.edu>
Fri, 25 Apr 2025 18:22:54 +0000 (11:22 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Fri, 25 Apr 2025 18:22:54 +0000 (11:22 -0700)
rust/pspp/src/output/cairo/driver.rs [new file with mode: 0644]
rust/pspp/src/output/cairo/mod.rs
rust/pspp/src/output/pivot/test.rs

diff --git a/rust/pspp/src/output/cairo/driver.rs b/rust/pspp/src/output/cairo/driver.rs
new file mode 100644 (file)
index 0000000..2129d24
--- /dev/null
@@ -0,0 +1,117 @@
+use std::{borrow::Cow, path::Path, sync::Arc};
+
+use cairo::{Context, PdfSurface};
+use enum_map::{enum_map, EnumMap};
+use pango::SCALE;
+
+use crate::output::{
+    cairo::{
+        fsm::{parse_font_style, CairoFsmStyle},
+        pager::{CairoPageStyle, CairoPager},
+    },
+    driver::Driver,
+    page::Setup,
+    pivot::{Color, Coord2, FontStyle},
+    Item,
+};
+
+use crate::output::pivot::Axis2;
+
+pub struct CairoDriver {
+    fsm_style: Arc<CairoFsmStyle>,
+    page_style: Arc<CairoPageStyle>,
+    pager: Option<CairoPager>,
+    surface: PdfSurface,
+}
+
+impl CairoDriver {
+    pub fn new(path: impl AsRef<Path>) -> CairoDriver {
+        fn scale(inches: f64) -> usize {
+            (inches * 72.0 * SCALE as f64).max(0.0).round() as usize
+        }
+
+        let page_setup = Setup::default();
+        let printable = page_setup.printable_size();
+        let page_style = CairoPageStyle {
+            margins: EnumMap::from_fn(|axis| {
+                [
+                    scale(page_setup.margins[axis][0]),
+                    scale(page_setup.margins[axis][1]),
+                ]
+            }),
+            headings: page_setup.headings.clone(),
+            initial_page_number: page_setup.initial_page_number,
+        };
+        let size = Coord2::new(scale(printable[Axis2::X]), scale(printable[Axis2::Y]));
+        let font = FontStyle {
+            bold: false,
+            italic: false,
+            underline: false,
+            markup: false,
+            font: "Sans Serif".into(),
+            fg: [Color::BLACK, Color::BLACK],
+            bg: [Color::WHITE, Color::WHITE],
+            size: 10,
+        };
+        let font = parse_font_style(&font);
+        let fsm_style = CairoFsmStyle {
+            size,
+            min_break: enum_map! {
+                Axis2::X => size[Axis2::X] / 2,
+                Axis2::Y => size[Axis2::Y] / 2,
+            },
+            font,
+            fg: Color::BLACK,
+            use_system_colors: false,
+            object_spacing: scale(page_setup.object_spacing),
+            font_resolution: 72.0,
+        };
+        let surface = PdfSurface::new(
+            page_setup.paper[Axis2::X] * 72.0,
+            page_setup.paper[Axis2::Y] * 72.0,
+            path,
+        )
+        .unwrap();
+        Self {
+            fsm_style: Arc::new(fsm_style),
+            page_style: Arc::new(page_style),
+            pager: None,
+            surface,
+        }
+    }
+}
+
+impl Driver for CairoDriver {
+    fn name(&self) -> Cow<'static, str> {
+        Cow::from("cairo")
+    }
+
+    fn write(&mut self, item: &Arc<Item>) {
+        let pager = self.pager.get_or_insert_with(|| {
+            let mut pager = CairoPager::new(self.page_style.clone(), self.fsm_style.clone());
+            pager.add_page(Context::new(&self.surface).unwrap());
+            pager
+        });
+        pager.add_item(item.clone());
+        dbg!();
+        while pager.needs_new_page() {
+            dbg!();
+            pager.finish_page();
+            let context = Context::new(&self.surface).unwrap();
+            context.show_page().unwrap();
+            pager.add_page(context);
+        }
+        dbg!();
+    }
+}
+
+impl Drop for CairoDriver {
+    fn drop(&mut self) {
+        dbg!();
+        if self.pager.is_some() {
+            dbg!();
+            let context = Context::new(&self.surface).unwrap();
+            context.show_page().unwrap();
+        }
+    }
+}
index 0d431e2c05486e462ebc5dd23088cf9b699a27cc..d648caf08a1958528c57a00d6b10cb754dcf3e0a 100644 (file)
-use std::{borrow::Cow, path::Path, sync::Arc};
-
-use cairo::{Context, PdfSurface};
-use enum_map::{enum_map, EnumMap};
 use pango::SCALE;
 
-use crate::output::{
-    cairo::{
-        fsm::{parse_font_style, CairoFsmStyle},
-        pager::{CairoPageStyle, CairoPager},
-    },
-    driver::Driver,
-    page::Setup,
-    pivot::{Color, Coord2, FontStyle, HorzAlign},
-    Item,
-};
+use crate::output::pivot::HorzAlign;
 
-use super::pivot::Axis2;
 
+mod driver;
 pub mod fsm;
 pub mod pager;
 
-pub struct CairoDriver {
-    fsm_style: Arc<CairoFsmStyle>,
-    page_style: Arc<CairoPageStyle>,
-    pager: Option<CairoPager>,
-    surface: PdfSurface,
-}
-
-impl CairoDriver {
-    pub fn new(path: impl AsRef<Path>) -> CairoDriver {
-        fn scale(inches: f64) -> usize {
-            (inches * 72.0 * SCALE as f64).max(0.0).round() as usize
-        }
-
-        let page_setup = Setup::default();
-        let printable = page_setup.printable_size();
-        let page_style = CairoPageStyle {
-            margins: EnumMap::from_fn(|axis| {
-                [
-                    scale(page_setup.margins[axis][0]),
-                    scale(page_setup.margins[axis][1]),
-                ]
-            }),
-            headings: page_setup.headings.clone(),
-            initial_page_number: page_setup.initial_page_number,
-        };
-        let size = Coord2::new(scale(printable[Axis2::X]), scale(printable[Axis2::Y]));
-        let font = FontStyle {
-            bold: false,
-            italic: false,
-            underline: false,
-            markup: false,
-            font: "Sans Serif".into(),
-            fg: [Color::BLACK, Color::BLACK],
-            bg: [Color::WHITE, Color::WHITE],
-            size: 10,
-        };
-        let font = parse_font_style(&font);
-        let fsm_style = CairoFsmStyle {
-            size,
-            min_break: enum_map! {
-                Axis2::X => size[Axis2::X] / 2,
-                Axis2::Y => size[Axis2::Y] / 2,
-            },
-            font,
-            fg: Color::BLACK,
-            use_system_colors: false,
-            object_spacing: scale(page_setup.object_spacing),
-            font_resolution: 72.0,
-        };
-        let surface = PdfSurface::new(
-            page_setup.paper[Axis2::X] * 72.0,
-            page_setup.paper[Axis2::Y] * 72.0,
-            path,
-        )
-        .unwrap();
-        Self {
-            fsm_style: Arc::new(fsm_style),
-            page_style: Arc::new(page_style),
-            pager: None,
-            surface,
-        }
-    }
-}
-
-impl Driver for CairoDriver {
-    fn name(&self) -> Cow<'static, str> {
-        Cow::from("cairo")
-    }
-
-    fn write(&mut self, item: &Arc<Item>) {
-        let pager = self.pager.get_or_insert_with(|| {
-            let mut pager = CairoPager::new(self.page_style.clone(), self.fsm_style.clone());
-            pager.add_page(Context::new(&self.surface).unwrap());
-            pager
-        });
-        pager.add_item(item.clone());
-        dbg!();
-        while pager.needs_new_page() {
-            dbg!();
-            pager.finish_page();
-            let context = Context::new(&self.surface).unwrap();
-            context.show_page().unwrap();
-            pager.add_page(context);
-        }
-        dbg!();
-    }
-}
-
-impl Drop for CairoDriver {
-    fn drop(&mut self) {
-        dbg!();
-        if let Some(pager) = self.pager.take() {
-            dbg!();
-            let context = Context::new(&self.surface).unwrap();
-            context.show_page().unwrap();
-        }
-    }
-}
+pub use driver::CairoDriver;
 
 /// Conversion from 1/96" units ("pixels") to Cairo/Pango units.
 fn px_to_xr(x: usize) -> usize {
index 33c5f29f5f5562279ae1419efa001705b8e2a78f..3d6fe8ad2788f21a474f66ed69dc0012c4b650bc 100644 (file)
@@ -1,4 +1,4 @@
-use std::sync::Arc;
+use std::{path::Path, sync::Arc};
 
 use enum_map::EnumMap;
 
@@ -47,8 +47,9 @@ fn d1(title: &str, axis: Axis3) -> PivotTable {
 
 #[test]
 fn d1_c() {
-    assert_eq!(
-        d1("Columns", Axis3::X).to_string(),
+    assert_rendering(
+        "d1_c",
+        &d1("Columns", Axis3::X),
         "\
 Columns
 ╭────────╮
@@ -58,7 +59,7 @@ Columns
 ├──┼──┼──┤
 │ 0│ 1│ 2│
 ╰──┴──┴──╯
-"
+",
     );
 }
 
@@ -72,8 +73,9 @@ fn d1_pdf() {
 
 #[test]
 fn d1_r() {
-    assert_eq!(
-        d1("Rows", Axis3::Y).to_string(),
+    assert_rendering(
+        "d1_r",
+        &d1("Rows", Axis3::Y),
         "\
 Rows
 ╭──┬─╮
@@ -83,7 +85,7 @@ Rows
 │a2│1│
 │a3│2│
 ╰──┴─╯
-"
+",
     );
 }
 
@@ -127,7 +129,7 @@ fn d2(title: &str, axes: [Axis3; 2], dimension_labels: Option<LabelPosition>) ->
 }
 
 #[track_caller]
-fn assert_rendering(pivot_table: &PivotTable, expected: &str) {
+fn assert_rendering(name: &str, pivot_table: &PivotTable, expected: &str) {
     let actual = pivot_table.to_string();
     if actual != expected {
         eprintln!("Unexpected pivot table rendering:\n--- expected\n+++ actual");
@@ -140,11 +142,18 @@ fn assert_rendering(pivot_table: &PivotTable, expected: &str) {
         }
         panic!();
     }
+
+    if let Some(dir) = std::env::var_os("PSPP_TEST_OUTPUT_DIR") {
+        let mut cairo = CairoDriver::new(Path::new(&dir).join(name).with_extension(".pdf"));
+        let item = Arc::new(Item::new(Details::Table(Box::new(pivot_table.clone()))));
+        cairo.write(&item);
+    }
 }
 
 #[test]
 fn d2_cc() {
     assert_rendering(
+        "d2_cc",
         &d2("Columns", [Axis3::X, Axis3::X], None),
         "\
 Columns
@@ -162,6 +171,7 @@ Columns
 #[test]
 fn d2_cc_with_dim_labels() {
     assert_rendering(
+        "d2_cc_with_dim_labels",
         &d2("Columns", [Axis3::X, Axis3::X], Some(LabelPosition::Corner)),
         "\
 Columns
@@ -183,6 +193,7 @@ Columns
 #[test]
 fn d2_rr() {
     assert_rendering(
+        "d2_rr",
         &d2("Rows", [Axis3::Y, Axis3::Y], None),
         "\
 Rows
@@ -206,6 +217,7 @@ Rows
 #[test]
 fn d2_rr_with_corner_dim_labels() {
     assert_rendering(
+        "d2_rr_with_corner_dim_labels",
         &d2(
             "Rows - Corner",
             [Axis3::Y, Axis3::Y],
@@ -235,6 +247,7 @@ Rows - Corner
 #[test]
 fn d2_rr_with_nested_dim_labels() {
     assert_rendering(
+        "d2_rr_with_nested_dim_labels",
         &d2(
             "Rows - Nested",
             [Axis3::Y, Axis3::Y],
@@ -262,6 +275,7 @@ Rows - Nested
 #[test]
 fn d2_cr() {
     assert_rendering(
+        "d2_cr",
         &d2("Column x Row", [Axis3::X, Axis3::Y], None),
         "\
 Column x Row
@@ -279,6 +293,7 @@ Column x Row
 #[test]
 fn d2_cr_with_corner_dim_labels() {
     assert_rendering(
+        "d2_cr_with_corner_dim_labels",
         &d2(
             "Column x Row - Corner",
             [Axis3::X, Axis3::Y],
@@ -302,6 +317,7 @@ Column x Row - Corner
 #[test]
 fn d2_cr_with_nested_dim_labels() {
     assert_rendering(
+        "d2_cr_with_nested_dim_labels",
         &d2(
             "Column x Row - Nested",
             [Axis3::X, Axis3::Y],
@@ -325,6 +341,7 @@ Column x Row - Nested
 #[test]
 fn d2_rc() {
     assert_rendering(
+        "d2_rc",
         &d2("Row x Column", [Axis3::Y, Axis3::X], None),
         "\
 Row x Column
@@ -342,6 +359,7 @@ Row x Column
 #[test]
 fn d2_rc_with_corner_dim_labels() {
     assert_rendering(
+        "d2_rc_with_corner_dim_labels",
         &d2(
             "Row x Column - Corner",
             [Axis3::Y, Axis3::X],
@@ -365,6 +383,7 @@ Row x Column - Corner
 #[test]
 fn d2_rc_with_nested_dim_labels() {
     assert_rendering(
+        "d2_rc_with_nested_dim_labels",
         &d2(
             "Row x Column - Nested",
             [Axis3::Y, Axis3::X],
@@ -389,6 +408,7 @@ Row x Column - Nested
 fn d2_cl() {
     let pivot_table = d2("Column x b1", [Axis3::X, Axis3::Z], None);
     assert_rendering(
+        "d2_cl-layer0",
         &pivot_table,
         "\
 Column x b1
@@ -405,6 +425,7 @@ b1
         .with_layer(&[1])
         .with_title(Value::new_text("Column x b2"));
     assert_rendering(
+        "d2_cl-layer1",
         &pivot_table,
         "\
 Column x b2
@@ -421,6 +442,7 @@ b2
         .with_all_layers()
         .with_title(Value::new_text("Column (All Layers)"));
     assert_rendering(
+        "d2_cl-all_layers",
         &pivot_table,
         "\
 Column (All Layers)
@@ -454,6 +476,7 @@ b3
 fn d2_rl() {
     let pivot_table = d2("Row x b1", [Axis3::Y, Axis3::Z], None);
     assert_rendering(
+        "d2_rl-layer0",
         &pivot_table,
         "\
 Row x b1
@@ -470,6 +493,7 @@ b1
         .with_layer(&[1])
         .with_title(Value::new_text("Row x b2"));
     assert_rendering(
+        "d2_rl-layer1",
         &pivot_table,
         "\
 Row x b2
@@ -486,6 +510,7 @@ b2
         .with_all_layers()
         .with_title(Value::new_text("Row (All Layers)"));
     assert_rendering(
+        "d2_rl-all_layers",
         &pivot_table,
         "\
 Row (All Layers)
@@ -549,6 +574,7 @@ fn d3() {
         }
     }
     assert_rendering(
+        "d3-layer0_0",
         &pt,
         "\
 Column x b1 x a1
@@ -564,6 +590,7 @@ a1
 
     let pt = pt.with_layer(&[0, 1]).with_title("Column x b2 x a1");
     assert_rendering(
+        "d3-layer0_1",
         &pt,
         "\
 Column x b2 x a1
@@ -579,6 +606,7 @@ a1
 
     let pt = pt.with_layer(&[1, 2]).with_title("Column x b3 x a2");
     assert_rendering(
+        "d3-layer1_2",
         &pt,
         "\
 Column x b3 x a2
@@ -598,6 +626,7 @@ fn title_and_caption() {
     let pivot_table =
         d2("Title", [Axis3::X, Axis3::Y], None).with_caption(Value::new_text("Caption"));
     assert_rendering(
+        "title_and_caption",
         &pivot_table,
         "\
 Title
@@ -614,6 +643,7 @@ Caption
 
     let pivot_table = pivot_table.with_show_title(false);
     assert_rendering(
+        "caption",
         &pivot_table,
         "\
 ╭──┬──┬──┬──╮
@@ -629,6 +659,7 @@ Caption
 
     let pivot_table = pivot_table.with_show_caption(false);
     assert_rendering(
+        "no_title_or_caption",
         &pivot_table,
         "\
 ╭──┬──┬──┬──╮
@@ -694,6 +725,7 @@ fn footnote_table(show_f0: bool) -> PivotTable {
 #[test]
 fn footnote_alphabetic_subscript() {
     assert_rendering(
+        "footnote_alphabetic_subscript",
         &footnote_table(true),
         "\
 Pivot Table with Alphabetic Subscript Footnotes[*]
@@ -721,6 +753,7 @@ fn footnote_alphabetic_superscript() {
     );
     pt.look_mut().footnote_marker_position = FootnoteMarkerPosition::Superscript;
     assert_rendering(
+        "footnote_alphabetic_superscript",
         &pt,
         "\
 Pivot Table with Alphabetic Superscript Footnotes[*]
@@ -748,6 +781,7 @@ fn footnote_numeric_subscript() {
     );
     pt.look_mut().footnote_marker_type = FootnoteMarkerType::Numeric;
     assert_rendering(
+        "footnote_numeric_subscript",
         &pt,
         "\
 Pivot Table with Numeric Subscript Footnotes[*]
@@ -776,6 +810,7 @@ fn footnote_numeric_superscript() {
     pt.look_mut().footnote_marker_type = FootnoteMarkerType::Numeric;
     pt.look_mut().footnote_marker_position = FootnoteMarkerPosition::Superscript;
     assert_rendering(
+        "footnote_numeric_superscript",
         &pt,
         "\
 Pivot Table with Numeric Superscript Footnotes[*]
@@ -797,6 +832,7 @@ Caption[*]
 #[test]
 fn footnote_hidden() {
     assert_rendering(
+        "footnote_hidden",
         &footnote_table(false),
         "\
 Pivot Table with Alphabetic Subscript Footnotes[*]
@@ -820,6 +856,7 @@ fn no_dimension() {
         .with_title("No Dimensions")
         .with_look(Arc::new(test_look()));
     assert_rendering(
+        "no_dimension",
         &pivot_table,
         "No Dimensions
 ╭╮
@@ -836,14 +873,18 @@ fn empty_dimensions() {
     let pivot_table = PivotTable::new(vec![d1])
         .with_title("One Empty Dimension")
         .with_look(look.clone());
-    assert_rendering(&pivot_table, "One Empty Dimension\n");
+    assert_rendering("one_empty_dimension", &pivot_table, "One Empty Dimension\n");
 
     let d1 = (Axis3::X, Dimension::new(Group::new("a")));
     let d2 = (Axis3::X, Dimension::new(Group::new("b").with_label_shown()));
     let pivot_table = PivotTable::new(vec![d1, d2])
         .with_title("Two Empty Dimensions")
         .with_look(look.clone());
-    assert_rendering(&pivot_table, "Two Empty Dimensions\n");
+    assert_rendering(
+        "two_empty_dimensions",
+        &pivot_table,
+        "Two Empty Dimensions\n",
+    );
 
     let d1 = (Axis3::X, Dimension::new(Group::new("a")));
     let d2 = (Axis3::X, Dimension::new(Group::new("b").with_label_shown()));
@@ -854,7 +895,11 @@ fn empty_dimensions() {
     let pivot_table = PivotTable::new(vec![d1, d2, d3])
         .with_title("Three Dimensions, Two Empty")
         .with_look(look.clone());
-    assert_rendering(&pivot_table, "Three Dimensions, Two Empty\n");
+    assert_rendering(
+        "three_dimensions_two_empty",
+        &pivot_table,
+        "Three Dimensions, Two Empty\n",
+    );
 }
 
 #[test]
@@ -879,6 +924,7 @@ fn empty_groups() {
     }
     let pivot_table = pt.with_look(Arc::new(test_look().with_omit_empty(false)));
     assert_rendering(
+        "empty_groups",
         &pivot_table,
         "\
 Empty Groups
@@ -962,6 +1008,7 @@ fn dimension_borders_1() {
         true,
     );
     assert_rendering(
+        "dimension_borders_1",
         &pivot_table,
         "\
 Dimension Borders 1
@@ -1001,6 +1048,7 @@ fn dimension_borders_2() {
         true,
     );
     assert_rendering(
+        "dimension_borders_2",
         &pivot_table,
         "\
 Dimension Borders 2
@@ -1036,6 +1084,7 @@ fn category_borders_1() {
         true,
     );
     assert_rendering(
+        "category_borders_1",
         &pivot_table,
         "\
 Category Borders 1
@@ -1078,6 +1127,7 @@ fn category_borders_2() {
         true,
     );
     assert_rendering(
+        "category_borders_2",
         &pivot_table,
         "\
 Category Borders 2
@@ -1119,6 +1169,7 @@ fn category_and_dimension_borders_1() {
         true,
     );
     assert_rendering(
+        "category_and_dimension_borders_1",
         &pivot_table,
         "\
 Category and Dimension Borders 1
@@ -1163,6 +1214,7 @@ fn category_and_dimension_borders_2() {
         true,
     );
     assert_rendering(
+        "category_and_dimension_borders_2",
         &pivot_table,
         "\
 Category and Dimension Borders 2
@@ -1212,6 +1264,7 @@ fn category_and_dimension_borders_3() {
         false,
     );
     assert_rendering(
+        "category_and_dimension_borders_3",
         &pivot_table,
         "\
 Category and Dimension Borders 3
@@ -1323,6 +1376,7 @@ fn small_numbers() {
     pt.insert_number(&[9, 1, 1], Some(-0.000000001), Class::Residual);
     let pivot_table = pt.with_look(Arc::new(test_look()));
     assert_rendering(
+        "small_numbers",
         &pivot_table,
         "\
 small numbers