--- /dev/null
+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();
+ }
+ }
+}
-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 {
-use std::sync::Arc;
+use std::{path::Path, sync::Arc};
use enum_map::EnumMap;
#[test]
fn d1_c() {
- assert_eq!(
- d1("Columns", Axis3::X).to_string(),
+ assert_rendering(
+ "d1_c",
+ &d1("Columns", Axis3::X),
"\
Columns
╭────────╮
├──┼──┼──┤
│ 0│ 1│ 2│
╰──┴──┴──╯
-"
+",
);
}
#[test]
fn d1_r() {
- assert_eq!(
- d1("Rows", Axis3::Y).to_string(),
+ assert_rendering(
+ "d1_r",
+ &d1("Rows", Axis3::Y),
"\
Rows
╭──┬─╮
│a2│1│
│a3│2│
╰──┴─╯
-"
+",
);
}
}
#[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");
}
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
#[test]
fn d2_cc_with_dim_labels() {
assert_rendering(
+ "d2_cc_with_dim_labels",
&d2("Columns", [Axis3::X, Axis3::X], Some(LabelPosition::Corner)),
"\
Columns
#[test]
fn d2_rr() {
assert_rendering(
+ "d2_rr",
&d2("Rows", [Axis3::Y, Axis3::Y], None),
"\
Rows
#[test]
fn d2_rr_with_corner_dim_labels() {
assert_rendering(
+ "d2_rr_with_corner_dim_labels",
&d2(
"Rows - Corner",
[Axis3::Y, Axis3::Y],
#[test]
fn d2_rr_with_nested_dim_labels() {
assert_rendering(
+ "d2_rr_with_nested_dim_labels",
&d2(
"Rows - Nested",
[Axis3::Y, Axis3::Y],
#[test]
fn d2_cr() {
assert_rendering(
+ "d2_cr",
&d2("Column x Row", [Axis3::X, Axis3::Y], None),
"\
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],
#[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],
#[test]
fn d2_rc() {
assert_rendering(
+ "d2_rc",
&d2("Row x Column", [Axis3::Y, Axis3::X], None),
"\
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],
#[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],
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
.with_layer(&[1])
.with_title(Value::new_text("Column x b2"));
assert_rendering(
+ "d2_cl-layer1",
&pivot_table,
"\
Column x b2
.with_all_layers()
.with_title(Value::new_text("Column (All Layers)"));
assert_rendering(
+ "d2_cl-all_layers",
&pivot_table,
"\
Column (All Layers)
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
.with_layer(&[1])
.with_title(Value::new_text("Row x b2"));
assert_rendering(
+ "d2_rl-layer1",
&pivot_table,
"\
Row x b2
.with_all_layers()
.with_title(Value::new_text("Row (All Layers)"));
assert_rendering(
+ "d2_rl-all_layers",
&pivot_table,
"\
Row (All Layers)
}
}
assert_rendering(
+ "d3-layer0_0",
&pt,
"\
Column x b1 x 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
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
let pivot_table =
d2("Title", [Axis3::X, Axis3::Y], None).with_caption(Value::new_text("Caption"));
assert_rendering(
+ "title_and_caption",
&pivot_table,
"\
Title
let pivot_table = pivot_table.with_show_title(false);
assert_rendering(
+ "caption",
&pivot_table,
"\
╭──┬──┬──┬──╮
let pivot_table = pivot_table.with_show_caption(false);
assert_rendering(
+ "no_title_or_caption",
&pivot_table,
"\
╭──┬──┬──┬──╮
#[test]
fn footnote_alphabetic_subscript() {
assert_rendering(
+ "footnote_alphabetic_subscript",
&footnote_table(true),
"\
Pivot Table with Alphabetic Subscript Footnotes[*]
);
pt.look_mut().footnote_marker_position = FootnoteMarkerPosition::Superscript;
assert_rendering(
+ "footnote_alphabetic_superscript",
&pt,
"\
Pivot Table with Alphabetic Superscript Footnotes[*]
);
pt.look_mut().footnote_marker_type = FootnoteMarkerType::Numeric;
assert_rendering(
+ "footnote_numeric_subscript",
&pt,
"\
Pivot Table with Numeric Subscript Footnotes[*]
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[*]
#[test]
fn footnote_hidden() {
assert_rendering(
+ "footnote_hidden",
&footnote_table(false),
"\
Pivot Table with Alphabetic Subscript Footnotes[*]
.with_title("No Dimensions")
.with_look(Arc::new(test_look()));
assert_rendering(
+ "no_dimension",
&pivot_table,
"No 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()));
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]
}
let pivot_table = pt.with_look(Arc::new(test_look().with_omit_empty(false)));
assert_rendering(
+ "empty_groups",
&pivot_table,
"\
Empty Groups
true,
);
assert_rendering(
+ "dimension_borders_1",
&pivot_table,
"\
Dimension Borders 1
true,
);
assert_rendering(
+ "dimension_borders_2",
&pivot_table,
"\
Dimension Borders 2
true,
);
assert_rendering(
+ "category_borders_1",
&pivot_table,
"\
Category Borders 1
true,
);
assert_rendering(
+ "category_borders_2",
&pivot_table,
"\
Category Borders 2
true,
);
assert_rendering(
+ "category_and_dimension_borders_1",
&pivot_table,
"\
Category and Dimension Borders 1
true,
);
assert_rendering(
+ "category_and_dimension_borders_2",
&pivot_table,
"\
Category and Dimension Borders 2
false,
);
assert_rendering(
+ "category_and_dimension_borders_3",
&pivot_table,
"\
Category and Dimension Borders 3
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