use std::sync::Arc;
use cairo::{Context, RecordingSurface};
use enum_map::EnumMap;
use pango::{FontDescription, Layout};
use crate::output::{
cairo::{
fsm::{CairoFsm, CairoFsmStyle},
horz_align_to_pango, xr_to_pt,
},
page::Heading,
pivot::Axis2,
Item, ItemCursor,
};
#[derive(Clone, Debug)]
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<ItemCursor>,
context: Option<Context>,
fsm: Option<CairoFsm>,
y: usize,
}
impl CairoPager {
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) {
let fsm_style = Arc::make_mut(&mut fsm_style);
let page_style = Arc::make_mut(&mut page_style);
#[allow(clippy::needless_range_loop)]
for i in 0..2 {
page_style.margins[Axis2::Y][i] += heading_heights[i];
}
fsm_style.size[Axis2::Y] -= total;
}
Self {
page_style,
fsm_style,
page_index: 0,
heading_heights,
iter: None,
context: None,
fsm: None,
y: 0,
}
}
pub fn add_page(&mut self, context: Context) {
assert!(self.context.is_none());
context.save().unwrap();
self.y = 0;
context.translate(
xr_to_pt(self.page_style.margins[Axis2::X][0]),
xr_to_pt(self.page_style.margins[Axis2::Y][0]),
);
let page_number = self.page_index + self.page_style.initial_page_number;
self.page_index += 1;
if self.heading_heights[0] > 0 {
render_heading(
&context,
&self.fsm_style.font,
&self.page_style.headings[0],
page_number,
self.fsm_style.size[Axis2::X],
0,
self.fsm_style.font_resolution,
);
}
if self.heading_heights[0] > 0 {
render_heading(
&context,
&self.fsm_style.font,
&self.page_style.headings[1],
page_number,
self.fsm_style.size[Axis2::X],
self.fsm_style.size[Axis2::Y] + self.fsm_style.object_spacing,
self.fsm_style.font_resolution,
);
}
self.context = Some(context);
self.run();
}
pub fn finish_page(&mut self) {
if let Some(context) = self.context.take() {
context.restore().unwrap();
}
}
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) {
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 {
let fsm = match &mut self.fsm {
Some(fsm) => fsm,
None => {
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))
}
};
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;
}
}
}
}
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 {
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(y + base_y));
pangocairo::functions::show_layout(context, &layout);
context.restore().unwrap();
y += layout.height() as usize;
}
y
}