"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",
+ "heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.87",
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "715601f8f02e71baef9c1f94a657a9a77c192aea6097cf9ae7e5e177cd8cde68"
dependencies = [
- "heck",
+ "heck 0.5.0",
"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",
+ "heck 0.5.0",
"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"] }
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::{
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;
/// 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
pub struct CairoFsm {
style: Arc<CairoFsmStyle>,
params: Params,
- context: Context,
+ item: Arc<Item>,
+ layer_iterator: Option<Box<dyn Iterator<Item = SmallVec<[usize; 4]>>>>,
+ pager: Option<Pager>,
}
impl CairoFsm {
- pub fn new(style: Arc<CairoFsmStyle>, printing: bool, surface: &Surface) -> Self {
- let context = Context::new(surface).unwrap();
+ pub fn new(
+ style: Arc<CairoFsmStyle>,
+ printing: bool,
+ context: Context,
+ item: Arc<Item>,
+ ) -> Self {
let params = Params {
size: style.size,
font_size: {
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::<i32>()
+ .max(0) as usize,
+ )
}
pub fn parse_font_style(font_style: &FontStyle) -> FontDescription {
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 };
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();
}
}
-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::<i32>()
- .max(0) as usize,
- )
-}
-
-impl Device for CairoFsm {
+impl Device for CairoDevice<'_> {
fn params(&self) -> &Params {
&self.params
}
use std::{borrow::Cow, path::Path, sync::Arc};
-use cairo::PdfSurface;
+use cairo::{Context, PdfSurface};
use enum_map::enum_map;
use pango::SCALE;
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,
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)),
+ }
}
}
}
}
+/// 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;
--- /dev/null
+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<Axis2, [usize; 2]>,
+ pub headings: [Heading; 2],
+ pub initial_page_number: i32,
+}
+
+pub struct CairoPager {
+ page_style: Arc<CairoPageStyle>,
+ fsm_style: Arc<CairoFsmStyle>,
+ page_index: i32,
+ heading_heights: [usize; 2],
+ iter: Option<OwnedItemCursor>,
+}
+
+impl CairoPager {
+ fn new(page_style: Arc<CairoPageStyle>, fsm_style: Arc<CairoFsmStyle>) -> 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
+}
/// Other logging.
Log,
}
+
+pub struct ItemCursor<'a> {
+ cur: Option<&'a Item>,
+ stack: Vec<&'a [Arc<Item>]>,
+}
+
+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<Item>]) {
+ 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<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())
+ }
+}
/// will visit all values of the layer axis.
///
/// - Otherwise, the iterator will just visit `self.current_layer`.
- pub fn layers(&self, print: bool) -> Box<dyn Iterator<Item = SmallVec<[usize; 4]>> + '_> {
+ pub fn layers(&self, print: bool) -> Box<dyn Iterator<Item = SmallVec<[usize; 4]>>> {
if print && self.look.print_all_layers {
Box::new(self.axis_values(Axis3::Z))
} else {
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};
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]
xr_pager_destroy (struct xr_pager *p)
{
if (p)
- {
+ {x
free (p->nodes);
xr_page_style_unref (p->page_style);