cairo driver compiles
authorBen Pfaff <blp@cs.stanford.edu>
Fri, 25 Apr 2025 00:52:08 +0000 (17:52 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Fri, 25 Apr 2025 00:52:08 +0000 (17:52 -0700)
rust/Cargo.lock
rust/pspp/Cargo.toml
rust/pspp/src/output/cairo/fsm.rs
rust/pspp/src/output/cairo/mod.rs
rust/pspp/src/output/cairo/pager.rs
rust/pspp/src/output/mod.rs

index 691a5fa37e53f35b0e38ff96b9b1aa282277f43b..625ffceed7bcc25e13017d78b1c3dcb1231fcf1f 100644 (file)
@@ -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"
index d28b866d54751180c70c7ac22161839a9eff50f3..3dc3a805cd398224a7572c7fb7888320a9b061ba 100644 (file)
@@ -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"] }
index 7df9ba9a14146763c2e5aac540ac63714707bd4e..4a5ea31fbd484b5d9153ccf9bb6469e9e8f56bef 100644 (file)
@@ -77,7 +77,7 @@ impl CairoFsm {
     pub fn new(
         style: Arc<CairoFsmStyle>,
         printing: bool,
-        context: Context,
+        context: &Context,
         item: Arc<Item>,
     ) -> Self {
         let params = Params {
@@ -110,54 +110,59 @@ impl CairoFsm {
         let device = CairoDevice {
             style: &style,
             params: &params,
-            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) {
index d8531c35c38708815c2fd4f1f59745979ef7647e..0d431e2c05486e462ebc5dd23088cf9b699a27cc 100644 (file)
@@ -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<CairoFsmStyle>,
+    page_style: Arc<CairoPageStyle>,
+    pager: Option<CairoPager>,
+    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<Item>) {
-        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();
         }
     }
 }
index 54d0221b8d0e74a99545ec510505747db2b33a99..9fa579ef2a225b69242e46a4c5e5574221fab5d7 100644 (file)
@@ -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<CairoFsmStyle>,
     page_index: i32,
     heading_heights: [usize; 2],
-    iter: Option<OwnedItemCursor>,
+    iter: Option<ItemCursor>,
     context: Option<Context>,
     fsm: Option<CairoFsm>,
     y: usize,
 }
 
 impl CairoPager {
-    fn new(mut page_style: Arc<CairoPageStyle>, mut fsm_style: Arc<CairoFsmStyle>) -> Self {
+    pub fn new(mut page_style: Arc<CairoPageStyle>, mut fsm_style: Arc<CairoFsmStyle>) -> Self {
         let heading_heights = measure_headings(&page_style, &fsm_style);
         let total = heading_heights.iter().sum::<usize>();
         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<Item>) {
-        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<Item>) {
+        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;
             }
         }
     }
index 0e28b544f7431747f1074e4b47b39d190c34b6c2..7f23f8aeb41297054346de84e5315f35dc231028 100644 (file)
@@ -63,6 +63,13 @@ pub enum Details {
 }
 
 impl Details {
+    pub fn as_group(&self) -> Option<&[Arc<Item>]> {
+        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<Item>]>,
+pub struct ItemCursor {
+    cur: Option<Arc<Item>>,
+    stack: Vec<(Arc<Item>, usize)>,
 }
 
-impl<'a> ItemCursor<'a> {
-    pub fn new(start: &'a Item) -> Self {
+impl ItemCursor {
+    pub fn new(start: Arc<Item>) -> Self {
         Self {
             cur: Some(start),
             stack: Vec::new(),
         }
     }
 
-    pub fn cur(&self) -> Option<&Item> {
-        self.cur
-    }
-
-    fn next_children(&mut self, children: &'a [Arc<Item>]) {
-        if children.len() > 1 {
-            self.stack.push(&children[1..]);
-        }
-        self.cur = Some(&children[0]);
+    pub fn cur(&self) -> Option<&Arc<Item>> {
+        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<Item>,
-    #[borrows(root)]
-    #[covariant]
-    cursor: ItemCursor<'this>,
-}
-
-struct OwnedItemCursor(OwnedItemCursorInner);
-
-impl OwnedItemCursor {
-    pub fn new(item: Arc<Item>) -> 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())
-    }
-}