From 28f23266cf3ebb932bf8fb165d019902270de4e7 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Thu, 24 Apr 2025 17:52:08 -0700 Subject: [PATCH] cairo driver compiles --- rust/Cargo.lock | 68 ++--------------------- rust/pspp/Cargo.toml | 1 - rust/pspp/src/output/cairo/fsm.rs | 38 ++++++++----- rust/pspp/src/output/cairo/mod.rs | 65 ++++++++++++++++------ rust/pspp/src/output/cairo/pager.rs | 72 +++++++++++++++++++------ rust/pspp/src/output/mod.rs | 83 ++++++++++------------------- 6 files changed, 161 insertions(+), 166 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 691a5fa37e..625ffceed7 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -26,12 +26,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "aliasable" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" - [[package]] name = "android-tzdata" version = "0.1.1" @@ -318,7 +312,7 @@ version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "syn 2.0.87", @@ -694,7 +688,7 @@ version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "715601f8f02e71baef9c1f94a657a9a77c192aea6097cf9ae7e5e177cd8cde68" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro-crate", "proc-macro2", "quote", @@ -728,12 +722,6 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -1063,30 +1051,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "ouroboros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" -dependencies = [ - "aliasable", - "ouroboros_macro", - "static_assertions", -] - -[[package]] -name = "ouroboros_macro" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.87", -] - [[package]] name = "owo-colors" version = "3.5.0" @@ -1252,19 +1216,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", - "version_check", - "yansi", -] - [[package]] name = "pspp" version = "1.0.0" @@ -1298,7 +1249,6 @@ dependencies = [ "num-derive", "num-traits", "ordered-float", - "ouroboros", "pango", "pangocairo", "pspp-derive", @@ -1566,12 +1516,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strsim" version = "0.11.1" @@ -1607,7 +1551,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005" dependencies = [ "cfg-expr", - "heck 0.5.0", + "heck", "pkg-config", "toml", "version-compare", @@ -2197,12 +2141,6 @@ dependencies = [ "bitflags 2.6.0", ] -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - [[package]] name = "zerocopy" version = "0.8.24" diff --git a/rust/pspp/Cargo.toml b/rust/pspp/Cargo.toml index d28b866d54..3dc3a805cd 100644 --- a/rust/pspp/Cargo.toml +++ b/rust/pspp/Cargo.toml @@ -45,7 +45,6 @@ derive_more = { version = "2.0.1", features = ["debug"] } cairo-rs = { version = "0.20.7", features = ["ps", "png", "pdf", "svg"] } pango = "0.20.9" pangocairo = "0.20.7" -ouroboros = "0.18.5" [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.48.0", features = ["Win32_Globalization"] } diff --git a/rust/pspp/src/output/cairo/fsm.rs b/rust/pspp/src/output/cairo/fsm.rs index 7df9ba9a14..4a5ea31fbd 100644 --- a/rust/pspp/src/output/cairo/fsm.rs +++ b/rust/pspp/src/output/cairo/fsm.rs @@ -77,7 +77,7 @@ impl CairoFsm { pub fn new( style: Arc, printing: bool, - context: Context, + context: &Context, item: Arc, ) -> Self { let params = Params { @@ -110,54 +110,59 @@ impl CairoFsm { let device = CairoDevice { style: &style, params: ¶ms, - context: &context, + context, }; - match &item.details { + let (layer_iterator, pager) = match &item.details { Details::Table(pivot_table) => { let mut layer_iterator = pivot_table.layers(printing); let layer_indexes = layer_iterator.next(); ( - layer_iterator, + Some(layer_iterator), Some(Pager::new( &device, pivot_table, layer_indexes.as_ref().map(|indexes| indexes.as_slice()), )), - ); + ) } - _ => (), + _ => (None, None), }; Self { style, params, item, - layer_iterator: None, - pager: None, + layer_iterator, + pager, } } - pub fn draw_slice(&mut self, context: Context, space: usize) { + pub fn draw_slice(&mut self, context: &Context, space: usize) -> usize { debug_assert!(self.params.printing); context.save().unwrap(); - match &self.item.details { - Details::Table(_) => self.draw_table(&context, space), + let used = match &self.item.details { + Details::Table(_) => self.draw_table(context, space), _ => todo!(), }; context.restore().unwrap(); + + used } fn draw_table(&mut self, context: &Context, space: usize) -> usize { let Details::Table(pivot_table) = &self.item.details else { unreachable!() }; + let Some(pager) = &mut self.pager else { + return 0; + }; let mut device = CairoDevice { style: &self.style, params: &self.params, context, }; - let mut used = self.pager.as_mut().unwrap().draw_next(&mut device, space); - if !self.pager.as_mut().unwrap().has_next(&device) { + let mut used = pager.draw_next(&mut device, space); + if !pager.has_next(&device) { match self.layer_iterator.as_mut().unwrap().next() { Some(layer_indexes) => { self.pager = Some(Pager::new( @@ -178,6 +183,13 @@ impl CairoFsm { } used.min(space) } + + pub fn is_done(&self) -> bool { + match &self.item.details { + Details::Table(_) => self.pager.is_none(), + _ => todo!(), + } + } } fn xr_clip(context: &Context, clip: &Rect2) { diff --git a/rust/pspp/src/output/cairo/mod.rs b/rust/pspp/src/output/cairo/mod.rs index d8531c35c3..0d431e2c05 100644 --- a/rust/pspp/src/output/cairo/mod.rs +++ b/rust/pspp/src/output/cairo/mod.rs @@ -1,11 +1,14 @@ use std::{borrow::Cow, path::Path, sync::Arc}; use cairo::{Context, PdfSurface}; -use enum_map::enum_map; +use enum_map::{enum_map, EnumMap}; use pango::SCALE; use crate::output::{ - cairo::fsm::{parse_font_style, CairoFsm, CairoFsmStyle}, + cairo::{ + fsm::{parse_font_style, CairoFsmStyle}, + pager::{CairoPageStyle, CairoPager}, + }, driver::Driver, page::Setup, pivot::{Color, Coord2, FontStyle, HorzAlign}, @@ -18,7 +21,10 @@ pub mod fsm; pub mod pager; pub struct CairoDriver { - renderer: CairoFsm, + fsm_style: Arc, + page_style: Arc, + pager: Option, + surface: PdfSurface, } impl CairoDriver { @@ -29,6 +35,16 @@ impl CairoDriver { 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, @@ -41,7 +57,7 @@ impl CairoDriver { size: 10, }; let font = parse_font_style(&font); - let style = CairoFsmStyle { + let fsm_style = CairoFsmStyle { size, min_break: enum_map! { Axis2::X => size[Axis2::X] / 2, @@ -59,9 +75,11 @@ impl CairoDriver { path, ) .unwrap(); - let context = Context::new(surface).unwrap(); Self { - renderer: todo!(), //CairoFsm::new(style, true, context, item)), + fsm_style: Arc::new(fsm_style), + page_style: Arc::new(page_style), + pager: None, + surface, } } } @@ -72,16 +90,31 @@ impl Driver for CairoDriver { } fn write(&mut self, item: &Arc) { - match &item.details { - super::Details::Chart => todo!(), - super::Details::Image => todo!(), - super::Details::Group(_vec) => todo!(), - super::Details::Message(_diagnostic) => todo!(), - super::Details::PageBreak => todo!(), - super::Details::Table(_pivot_table) => { - todo!() - } - super::Details::Text(_text) => todo!(), + 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(); } } } diff --git a/rust/pspp/src/output/cairo/pager.rs b/rust/pspp/src/output/cairo/pager.rs index 54d0221b8d..9fa579ef2a 100644 --- a/rust/pspp/src/output/cairo/pager.rs +++ b/rust/pspp/src/output/cairo/pager.rs @@ -11,7 +11,7 @@ use crate::output::{ }, page::Heading, pivot::Axis2, - Item, OwnedItemCursor, + Item, ItemCursor, }; #[derive(Clone, Debug)] @@ -26,14 +26,14 @@ pub struct CairoPager { fsm_style: Arc, page_index: i32, heading_heights: [usize; 2], - iter: Option, + iter: Option, context: Option, fsm: Option, y: usize, } impl CairoPager { - fn new(mut page_style: Arc, mut fsm_style: Arc) -> Self { + pub fn new(mut page_style: Arc, mut fsm_style: Arc) -> Self { let heading_heights = measure_headings(&page_style, &fsm_style); let total = heading_heights.iter().sum::(); if (0..fsm_style.size[Axis2::Y]).contains(&total) { @@ -45,15 +45,18 @@ impl CairoPager { fsm_style.size[Axis2::Y] -= total; } Self { - heading_heights, page_style, fsm_style, page_index: 0, + heading_heights, iter: None, + context: None, + fsm: None, + y: 0, } } - fn add_page(&mut self, context: Context) { + pub fn add_page(&mut self, context: Context) { assert!(self.context.is_none()); context.save().unwrap(); self.y = 0; @@ -93,34 +96,69 @@ impl CairoPager { self.run(); } - fn finish_page(&mut self) { + pub fn finish_page(&mut self) { if let Some(context) = self.context.take() { context.restore().unwrap(); } } - fn add_item(&mut self, item: Arc) { - self.iter = Some(OwnedItemCursor::new(item)); + pub fn needs_new_page(&mut self) -> bool { + if self.iter.is_some() + && (self.context.is_none() || self.y >= self.fsm_style.size[Axis2::Y]) + { + self.finish_page(); + true + } else { + false + } + } + + pub fn add_item(&mut self, item: Arc) { + self.iter = Some(ItemCursor::new(item)); self.run(); } fn run(&mut self) { - if self.iter.is_none() || self.context.is_none() || self.y >= self.fsm_style.size[Axis2::Y] - { + let Some(context) = self.context.as_ref().cloned() else { + return; + }; + if self.iter.is_none() || self.y >= self.fsm_style.size[Axis2::Y] { return; } loop { // Make sure we've got an object to render. - while self.fsm.is_none() { - // If there are no remaining objects to render, then we're done. - if self.iter.as_mut().unwrap().cur().is_none() { - self.iter = None; - return; + let fsm = match &mut self.fsm { + Some(fsm) => fsm, + None => { + // If there are no remaining objects to render, then we're done. + let Some(iter) = self.iter.as_mut() else { + return; + }; + let Some(item) = iter.cur().cloned() else { + self.iter = None; + return; + }; + iter.next(); + self.fsm + .insert(CairoFsm::new(self.fsm_style.clone(), true, &context, item)) } + }; - // Prepare to render the current object. - let fsm = CairoFsm::new(self.fsm_style.clone(), true, self.context.clone(), self) + // Prepare to render the current object. + let chunk = fsm.draw_slice( + &context, + self.fsm_style.size[Axis2::Y].saturating_sub(self.y), + ); + self.y += chunk + self.fsm_style.object_spacing; + context.translate(0.0, xr_to_pt(chunk + self.fsm_style.object_spacing)); + + if fsm.is_done() { + self.fsm = None; + } else if chunk == 0 { + assert!(self.y > 0); + self.y = usize::MAX; + return; } } } diff --git a/rust/pspp/src/output/mod.rs b/rust/pspp/src/output/mod.rs index 0e28b544f7..7f23f8aeb4 100644 --- a/rust/pspp/src/output/mod.rs +++ b/rust/pspp/src/output/mod.rs @@ -63,6 +63,13 @@ pub enum Details { } impl Details { + pub fn as_group(&self) -> Option<&[Arc]> { + match self { + Self::Group(children) => Some(children.as_slice()), + _ => None, + } + } + pub fn command_name(&self) -> Option<&String> { match self { Details::Chart @@ -96,74 +103,42 @@ pub enum TextType { Log, } -pub struct ItemCursor<'a> { - cur: Option<&'a Item>, - stack: Vec<&'a [Arc]>, +pub struct ItemCursor { + cur: Option>, + stack: Vec<(Arc, usize)>, } -impl<'a> ItemCursor<'a> { - pub fn new(start: &'a Item) -> Self { +impl ItemCursor { + pub fn new(start: Arc) -> Self { Self { cur: Some(start), stack: Vec::new(), } } - pub fn cur(&self) -> Option<&Item> { - self.cur - } - - fn next_children(&mut self, children: &'a [Arc]) { - if children.len() > 1 { - self.stack.push(&children[1..]); - } - self.cur = Some(&children[0]); + pub fn cur(&self) -> Option<&Arc> { + self.cur.as_ref() } pub fn next(&mut self) { - if let Some(cur) = self.cur { - match &cur.details { - Details::Group(children) if !children.is_empty() => { - self.next_children(children.as_slice()); - } - _ => { - if let Some(children) = self.stack.pop() { - self.next_children(children); - } else { - self.cur = None; + let Some(cur) = self.cur.take() else { + return; + }; + match cur.details { + Details::Group(ref children) if !children.is_empty() => { + self.cur = Some(children[0].clone()); + self.stack.push((cur, 1)); + } + _ => { + while let Some((item, index)) = self.stack.pop() { + let children = item.details.as_group().unwrap(); + if index < children.len() { + self.cur = Some(children[index].clone()); + self.stack.push((item, index + 1)); + return; } } } } } } - -#[ouroboros::self_referencing] -struct OwnedItemCursorInner { - root: Arc, - #[borrows(root)] - #[covariant] - cursor: ItemCursor<'this>, -} - -struct OwnedItemCursor(OwnedItemCursorInner); - -impl OwnedItemCursor { - pub fn new(item: Arc) -> Self { - Self( - OwnedItemCursorInnerBuilder { - root: item, - cursor_builder: |root| ItemCursor::new(root), - } - .build(), - ) - } - - pub fn cur(&self) -> Option<&Item> { - self.0.with_cursor(|cursor| cursor.cur) - } - - pub fn next(&mut self) { - self.0.with_cursor_mut(|cursor| cursor.next()) - } -} -- 2.30.2