From: Ben Pfaff Date: Thu, 24 Apr 2025 16:28:05 +0000 (-0700) Subject: work on cairo driver X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3042b9f9005569aad3a5eecd87837778df5d0df4;p=pspp work on cairo driver --- diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 625ffceed7..691a5fa37e 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -26,6 +26,12 @@ 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" @@ -312,7 +318,7 @@ version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.87", @@ -688,7 +694,7 @@ version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "715601f8f02e71baef9c1f94a657a9a77c192aea6097cf9ae7e5e177cd8cde68" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro-crate", "proc-macro2", "quote", @@ -722,6 +728,12 @@ 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" @@ -1051,6 +1063,30 @@ 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" @@ -1216,6 +1252,19 @@ 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" @@ -1249,6 +1298,7 @@ dependencies = [ "num-derive", "num-traits", "ordered-float", + "ouroboros", "pango", "pangocairo", "pspp-derive", @@ -1516,6 +1566,12 @@ 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" @@ -1551,7 +1607,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005" dependencies = [ "cfg-expr", - "heck", + "heck 0.5.0", "pkg-config", "toml", "version-compare", @@ -2141,6 +2197,12 @@ 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 3dc3a805cd..d28b866d54 100644 --- a/rust/pspp/Cargo.toml +++ b/rust/pspp/Cargo.toml @@ -45,6 +45,7 @@ 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 d4d12d9fa9..c8009dfb3b 100644 --- a/rust/pspp/src/output/cairo/fsm.rs +++ b/rust/pspp/src/output/cairo/fsm.rs @@ -1,6 +1,6 @@ use std::{cmp::min, f64::consts::PI, fmt::Write, ops::DerefMut, sync::Arc}; -use cairo::{Context, Surface}; +use cairo::Context; use enum_map::{enum_map, EnumMap}; use itertools::Itertools; use pango::{ @@ -10,9 +10,11 @@ use pango::{ use pangocairo::functions::show_layout; use smallvec::{smallvec, SmallVec}; +use crate::output::cairo::{horz_align_to_pango, px_to_xr, xr_to_pt}; use crate::output::pivot::{Axis2, BorderStyle, Coord2, FontStyle, HorzAlign, Rect2, Stroke}; -use crate::output::render::{Device, DrawCell, Extreme, Params}; +use crate::output::render::{Device, DrawCell, Extreme, Pager, Params}; use crate::output::{pivot::Color, table::Content}; +use crate::output::{Details, Item}; /// Width of an ordinary line. const LINE_WIDTH: usize = LINE_SPACE / 2; @@ -20,19 +22,6 @@ const LINE_WIDTH: usize = LINE_SPACE / 2; /// Space between double lines. const LINE_SPACE: usize = SCALE as usize; -/// Conversion from 1/96" units ("pixels") to Cairo/Pango units. -fn px_to_xr(x: usize) -> usize { - x * 3 * (SCALE as usize * 72 / 96) / 3 -} - -fn xr_to_pt(x: usize) -> f64 { - x as f64 / SCALE as f64 -} - -fn xr_to_pango(x: usize) -> usize { - x -} - /// Conversion from 1/96" units ("pixels") to Cairo/Pango units. fn pxf_to_xr(x: f64) -> usize { (x * (SCALE as f64 * 72.0 / 96.0)).round() as usize @@ -78,12 +67,18 @@ impl CairoFsmStyle { pub struct CairoFsm { style: Arc, params: Params, - context: Context, + item: Arc, + layer_iterator: Option>>>, + pager: Option, } impl CairoFsm { - pub fn new(style: Arc, printing: bool, surface: &Surface) -> Self { - let context = Context::new(surface).unwrap(); + pub fn new( + style: Arc, + printing: bool, + context: Context, + item: Arc, + ) -> Self { let params = Params { size: style.size, font_size: { @@ -111,14 +106,122 @@ impl CairoFsm { can_adjust_break: false, // XXX can_scale: true, }; + let device = CairoDevice { + style: &style, + params: ¶ms, + context: &context, + }; + match &item.details { + Details::Table(pivot_table) => { + let mut layer_iterator = pivot_table.layers(printing); + let layer_indexes = layer_iterator.next(); + ( + layer_iterator, + Some(Pager::new( + &device, + pivot_table, + layer_indexes.as_ref().map(|indexes| indexes.as_slice()), + )), + ); + } + _ => (), + }; Self { style, params, + item, + layer_iterator: None, + pager: None, + } + } + + pub fn draw_slice(&mut self, context: Context, space: usize) { + debug_assert!(self.params.printing); + + context.save().unwrap(); + match &self.item.details { + Details::Table(_) => self.draw_table(&context, space), + _ => todo!(), + }; + context.restore().unwrap(); + } + + fn draw_table(&mut self, context: &Context, space: usize) -> usize { + let Details::Table(pivot_table) = &self.item.details else { + unreachable!() + }; + 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) { + match self.layer_iterator.as_mut().unwrap().next() { + Some(layer_indexes) => { + self.pager = Some(Pager::new( + &device, + pivot_table, + Some(layer_indexes.as_slice()), + )); + if pivot_table.look.paginate_layers { + used = space; + } else { + used += self.style.object_spacing; + } + } + _ => { + self.pager = None; + } + } } + used.min(space) + } +} + +fn xr_clip(context: &Context, clip: &Rect2) { + if clip[Axis2::X].end != usize::MAX || clip[Axis2::Y].end != usize::MAX { + let x0 = xr_to_pt(clip[Axis2::X].start); + let y0 = xr_to_pt(clip[Axis2::Y].start); + let x1 = xr_to_pt(clip[Axis2::X].end); + let y1 = xr_to_pt(clip[Axis2::Y].end); + context.rectangle(x0, y0, x1 - x0, y1 - y0); + context.clip(); } +} + +fn xr_set_color(context: &Context, color: &Color) { + fn as_frac(x: u8) -> f64 { + x as f64 / 255.0 + } + + context.set_source_rgba( + as_frac(color.r), + as_frac(color.g), + as_frac(color.b), + as_frac(color.alpha), + ); +} + +fn xr_fill_rectangle(context: &Context, rectangle: Rect2) { + context.new_path(); + context.set_line_width(xr_to_pt(LINE_WIDTH)); + + let x0 = xr_to_pt(rectangle[Axis2::X].start); + let y0 = xr_to_pt(rectangle[Axis2::Y].start); + let width = xr_to_pt(rectangle[Axis2::X].len()); + let height = xr_to_pt(rectangle[Axis2::Y].len()); + context.rectangle(x0, y0, width, height); + let _ = context.fill(); +} - //pub fn render(&mut self, table: &PivotTable) {} +fn margin(cell: &DrawCell, axis: Axis2) -> usize { + px_to_xr( + cell.style.cell_style.margins[axis] + .iter() + .sum::() + .max(0) as usize, + ) } pub fn parse_font_style(font_style: &FontStyle) -> FontDescription { @@ -181,7 +284,13 @@ fn avoid_decimal_split(mut s: String) -> String { s } -impl CairoFsm { +struct CairoDevice<'a> { + style: &'a CairoFsmStyle, + params: &'a Params, + context: &'a Context, +} + +impl CairoDevice<'_> { fn layout_cell(&self, cell: &DrawCell, mut bb: Rect2, clip: &Rect2) -> Coord2 { // XXX rotation //let h = if cell.rotate { Axis2::Y } else { Axis2::X }; @@ -304,15 +413,11 @@ impl CairoFsm { layout.set_attributes(attrs.as_ref()); layout.set_text(&body); - layout.set_alignment(match horz_align { - HorzAlign::Right | HorzAlign::Decimal { .. } => pango::Alignment::Right, - HorzAlign::Left => pango::Alignment::Left, - HorzAlign::Center => pango::Alignment::Center, - }); + layout.set_alignment(horz_align_to_pango(horz_align)); if bb[Axis2::X].end == usize::MAX { layout.set_width(-1); } else { - layout.set_width(xr_to_pango(bb[Axis2::X].len()) as i32); + layout.set_width(bb[Axis2::X].len() as i32); } let size = layout.size(); @@ -373,52 +478,7 @@ impl CairoFsm { } } -fn xr_clip(context: &Context, clip: &Rect2) { - if clip[Axis2::X].end != usize::MAX || clip[Axis2::Y].end != usize::MAX { - let x0 = xr_to_pt(clip[Axis2::X].start); - let y0 = xr_to_pt(clip[Axis2::Y].start); - let x1 = xr_to_pt(clip[Axis2::X].end); - let y1 = xr_to_pt(clip[Axis2::Y].end); - context.rectangle(x0, y0, x1 - x0, y1 - y0); - context.clip(); - } -} - -fn xr_set_color(context: &Context, color: &Color) { - fn as_frac(x: u8) -> f64 { - x as f64 / 255.0 - } - - context.set_source_rgba( - as_frac(color.r), - as_frac(color.g), - as_frac(color.b), - as_frac(color.alpha), - ); -} - -fn xr_fill_rectangle(context: &Context, rectangle: Rect2) { - context.new_path(); - context.set_line_width(xr_to_pt(LINE_WIDTH)); - - let x0 = xr_to_pt(rectangle[Axis2::X].start); - let y0 = xr_to_pt(rectangle[Axis2::Y].start); - let width = xr_to_pt(rectangle[Axis2::X].len()); - let height = xr_to_pt(rectangle[Axis2::Y].len()); - context.rectangle(x0, y0, width, height); - let _ = context.fill(); -} - -fn margin(cell: &DrawCell, axis: Axis2) -> usize { - px_to_xr( - cell.style.cell_style.margins[axis] - .iter() - .sum::() - .max(0) as usize, - ) -} - -impl Device for CairoFsm { +impl Device for CairoDevice<'_> { fn params(&self) -> &Params { &self.params } diff --git a/rust/pspp/src/output/cairo/mod.rs b/rust/pspp/src/output/cairo/mod.rs index 5649ca9a68..d8531c35c3 100644 --- a/rust/pspp/src/output/cairo/mod.rs +++ b/rust/pspp/src/output/cairo/mod.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, path::Path, sync::Arc}; -use cairo::PdfSurface; +use cairo::{Context, PdfSurface}; use enum_map::enum_map; use pango::SCALE; @@ -8,13 +8,14 @@ use crate::output::{ cairo::fsm::{parse_font_style, CairoFsm, CairoFsmStyle}, driver::Driver, page::Setup, - pivot::{Color, Coord2, FontStyle}, + pivot::{Color, Coord2, FontStyle, HorzAlign}, Item, }; use super::pivot::Axis2; pub mod fsm; +pub mod pager; pub struct CairoDriver { renderer: CairoFsm, @@ -58,8 +59,10 @@ impl CairoDriver { path, ) .unwrap(); - let renderer = CairoFsm::new(Arc::new(style), false, &surface); - Self { renderer } + let context = Context::new(surface).unwrap(); + Self { + renderer: todo!(), //CairoFsm::new(style, true, context, item)), + } } } @@ -83,6 +86,23 @@ impl Driver for CairoDriver { } } +/// Conversion from 1/96" units ("pixels") to Cairo/Pango units. +fn px_to_xr(x: usize) -> usize { + x * 3 * (SCALE as usize * 72 / 96) / 3 +} + +fn xr_to_pt(x: usize) -> f64 { + x as f64 / SCALE as f64 +} + +fn horz_align_to_pango(horz_align: HorzAlign) -> pango::Alignment { + match horz_align { + HorzAlign::Right | HorzAlign::Decimal { .. } => pango::Alignment::Right, + HorzAlign::Left => pango::Alignment::Left, + HorzAlign::Center => pango::Alignment::Center, + } +} + #[cfg(test)] mod test { use crate::output::cairo::CairoDriver; diff --git a/rust/pspp/src/output/cairo/pager.rs b/rust/pspp/src/output/cairo/pager.rs new file mode 100644 index 0000000000..e7f649da4e --- /dev/null +++ b/rust/pspp/src/output/cairo/pager.rs @@ -0,0 +1,93 @@ +use std::sync::Arc; + +use cairo::{Context, RecordingSurface}; +use enum_map::EnumMap; +use pango::{FontDescription, Layout}; + +use crate::output::{ + cairo::{fsm::CairoFsmStyle, horz_align_to_pango, xr_to_pt}, + page::Heading, + pivot::Axis2, + OwnedItemCursor, +}; + +pub struct CairoPageStyle { + pub margins: EnumMap, + pub headings: [Heading; 2], + pub initial_page_number: i32, +} + +pub struct CairoPager { + page_style: Arc, + fsm_style: Arc, + page_index: i32, + heading_heights: [usize; 2], + iter: Option, +} + +impl CairoPager { + fn new(page_style: Arc, fsm_style: Arc) -> Self { + Self { + heading_heights: measure_headings(&page_style, &fsm_style), + page_style, + fsm_style, + page_index: 0, + iter: None, + } + } +} + +fn measure_headings(page_style: &CairoPageStyle, fsm_style: &CairoFsmStyle) -> [usize; 2] { + let surface = RecordingSurface::create(cairo::Content::Color, None).unwrap(); + let context = Context::new(&surface).unwrap(); + + let mut heading_heights = Vec::with_capacity(2); + for heading in &page_style.headings { + let mut height = render_heading( + &context, + &fsm_style.font, + heading, + -1, + fsm_style.size[Axis2::X], + 0, + fsm_style.font_resolution, + ); + if height > 0 { + height += fsm_style.object_spacing; + } + heading_heights.push(height); + } + heading_heights.try_into().unwrap() +} + +fn render_heading( + context: &Context, + font: &FontDescription, + heading: &Heading, + page_number: i32, + width: usize, + base_y: usize, + font_resolution: f64, +) -> usize { + let pangocairo_context = pangocairo::functions::create_context(context); + pangocairo::functions::context_set_resolution(&pangocairo_context, font_resolution); + let layout = Layout::new(&pangocairo_context); + layout.set_font_description(Some(font)); + + let mut y = 0; + for paragraph in &heading.0 { + // XXX substitute heading variables + layout.set_markup(¶graph.markup); + + layout.set_alignment(horz_align_to_pango(paragraph.horz_align)); + layout.set_width(width as i32); + + context.save().unwrap(); + context.translate(0.0, xr_to_pt(width)); + pangocairo::functions::show_layout(context, &layout); + context.restore().unwrap(); + + y += layout.height() as usize; + } + y +} diff --git a/rust/pspp/src/output/mod.rs b/rust/pspp/src/output/mod.rs index e2c194fcce..0e28b544f7 100644 --- a/rust/pspp/src/output/mod.rs +++ b/rust/pspp/src/output/mod.rs @@ -95,3 +95,75 @@ pub enum TextType { /// Other logging. Log, } + +pub struct ItemCursor<'a> { + cur: Option<&'a Item>, + stack: Vec<&'a [Arc]>, +} + +impl<'a> ItemCursor<'a> { + pub fn new(start: &'a 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]) { + if children.len() > 1 { + self.stack.push(&children[1..]); + } + self.cur = Some(&children[0]); + } + + 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; + } + } + } + } + } +} + +#[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()) + } +} diff --git a/rust/pspp/src/output/pivot/mod.rs b/rust/pspp/src/output/pivot/mod.rs index b1b4a55b49..c00f2cb574 100644 --- a/rust/pspp/src/output/pivot/mod.rs +++ b/rust/pspp/src/output/pivot/mod.rs @@ -1431,7 +1431,7 @@ impl PivotTable { /// will visit all values of the layer axis. /// /// - Otherwise, the iterator will just visit `self.current_layer`. - pub fn layers(&self, print: bool) -> Box> + '_> { + pub fn layers(&self, print: bool) -> Box>> { if print && self.look.print_all_layers { Box::new(self.axis_values(Axis3::Z)) } else { diff --git a/rust/pspp/src/output/pivot/test.rs b/rust/pspp/src/output/pivot/test.rs index 9bf93f6195..ad022a5554 100644 --- a/rust/pspp/src/output/pivot/test.rs +++ b/rust/pspp/src/output/pivot/test.rs @@ -2,11 +2,16 @@ use std::sync::Arc; use enum_map::EnumMap; -use crate::output::{cairo::CairoDriver, pivot::{ - Area, Axis2, Border, BorderStyle, Class, Color, Dimension, Footnote, FootnoteMarkerPosition, - FootnoteMarkerType, Footnotes, Group, HeadingRegion, LabelPosition, Look, PivotTable, - RowColBorder, Stroke, -}}; +use crate::output::{ + cairo::CairoDriver, + driver::Driver, + pivot::{ + Area, Axis2, Border, BorderStyle, Class, Color, Dimension, Footnote, + FootnoteMarkerPosition, FootnoteMarkerType, Footnotes, Group, HeadingRegion, LabelPosition, + Look, PivotTable, RowColBorder, Stroke, + }, + Details, Item, +}; use super::{Axis3, Value}; @@ -59,7 +64,9 @@ Columns fn d1_pdf() { let pt = d1("Columns", Axis3::X); - let cairo = CairoDriver::new("d1.pdf"); + let mut cairo = CairoDriver::new("d1.pdf"); + let item = Arc::new(Item::new(Details::Table(Box::new(pt)))); + cairo.write(&item); } #[test] diff --git a/src/output/cairo-pager.c b/src/output/cairo-pager.c index 1dbffd8270..a52d0b095a 100644 --- a/src/output/cairo-pager.c +++ b/src/output/cairo-pager.c @@ -246,7 +246,7 @@ void xr_pager_destroy (struct xr_pager *p) { if (p) - { + {x free (p->nodes); xr_page_style_unref (p->page_style);