"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"
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",
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "715601f8f02e71baef9c1f94a657a9a77c192aea6097cf9ae7e5e177cd8cde68"
dependencies = [
- "heck 0.5.0",
+ "heck",
"proc-macro-crate",
"proc-macro2",
"quote",
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"
"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"
"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"
"num-derive",
"num-traits",
"ordered-float",
- "ouroboros",
"pango",
"pangocairo",
"pspp-derive",
"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"
checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005"
dependencies = [
"cfg-expr",
- "heck 0.5.0",
+ "heck",
"pkg-config",
"toml",
"version-compare",
"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"
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"] }
pub fn new(
style: Arc<CairoFsmStyle>,
printing: bool,
- context: Context,
+ context: &Context,
item: Arc<Item>,
) -> Self {
let params = Params {
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(
}
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) {
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},
pub mod pager;
pub struct CairoDriver {
- renderer: CairoFsm,
+ fsm_style: Arc<CairoFsmStyle>,
+ page_style: Arc<CairoPageStyle>,
+ pager: Option<CairoPager>,
+ surface: PdfSurface,
}
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,
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,
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,
}
}
}
}
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();
}
}
}
},
page::Heading,
pivot::Axis2,
- Item, OwnedItemCursor,
+ Item, ItemCursor,
};
#[derive(Clone, Debug)]
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) {
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;
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;
}
}
}
}
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
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())
- }
-}