From eb5fc42f63028560be491bfd75ecddc89fc607ad Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Fri, 25 Apr 2025 11:22:54 -0700 Subject: [PATCH] start adding more pdf tests --- rust/pspp/src/output/cairo/driver.rs | 117 +++++++++++++++++++++++++++ rust/pspp/src/output/cairo/mod.rs | 116 +------------------------- rust/pspp/src/output/pivot/test.rs | 76 ++++++++++++++--- 3 files changed, 185 insertions(+), 124 deletions(-) create mode 100644 rust/pspp/src/output/cairo/driver.rs diff --git a/rust/pspp/src/output/cairo/driver.rs b/rust/pspp/src/output/cairo/driver.rs new file mode 100644 index 0000000000..2129d2409e --- /dev/null +++ b/rust/pspp/src/output/cairo/driver.rs @@ -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, + page_style: Arc, + pager: Option, + surface: PdfSurface, +} + +impl CairoDriver { + pub fn new(path: impl AsRef) -> 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) { + 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(); + } + } +} diff --git a/rust/pspp/src/output/cairo/mod.rs b/rust/pspp/src/output/cairo/mod.rs index 0d431e2c05..d648caf08a 100644 --- a/rust/pspp/src/output/cairo/mod.rs +++ b/rust/pspp/src/output/cairo/mod.rs @@ -1,123 +1,13 @@ -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, - page_style: Arc, - pager: Option, - surface: PdfSurface, -} - -impl CairoDriver { - pub fn new(path: impl AsRef) -> 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) { - 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 { diff --git a/rust/pspp/src/output/pivot/test.rs b/rust/pspp/src/output/pivot/test.rs index 33c5f29f5f..3d6fe8ad27 100644 --- a/rust/pspp/src/output/pivot/test.rs +++ b/rust/pspp/src/output/pivot/test.rs @@ -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) -> } #[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 -- 2.30.2