use comemo::{Track, Tracked, TrackedMut};
use crate::diag::SourceResult;
use crate::engine::{Engine, Route, Sink, Traced};
use crate::foundations::{Content, NativeElement, Resolve, Smart, StyleChain, Styles};
use crate::introspection::{
Counter, CounterDisplayElem, CounterKey, Introspector, Locator, LocatorLink, TagElem,
};
use crate::layout::{
layout_flow, layout_frame, Abs, AlignElem, Alignment, Axes, Binding, ColumnsElem,
Dir, Frame, HAlignment, Length, OuterVAlignment, PageElem, Paper, Region, Regions,
Rel, Sides, Size, VAlignment,
};
use crate::model::Numbering;
use crate::realize::Pair;
use crate::text::TextElem;
use crate::utils::Numeric;
use crate::visualize::Paint;
use crate::World;
#[derive(Clone)]
pub struct LayoutedPage {
pub inner: Frame,
pub margin: Sides<Abs>,
pub binding: Binding,
pub two_sided: bool,
pub header: Option<Frame>,
pub footer: Option<Frame>,
pub background: Option<Frame>,
pub foreground: Option<Frame>,
pub fill: Smart<Option<Paint>>,
pub numbering: Option<Numbering>,
}
pub fn layout_blank_page(
engine: &mut Engine,
locator: Locator,
initial: StyleChain,
) -> SourceResult<LayoutedPage> {
let layouted = layout_page_run(engine, &[], locator, initial)?;
Ok(layouted.into_iter().next().unwrap())
}
#[typst_macros::time(name = "page run")]
pub fn layout_page_run(
engine: &mut Engine,
children: &[Pair],
locator: Locator,
initial: StyleChain,
) -> SourceResult<Vec<LayoutedPage>> {
layout_page_run_impl(
engine.world,
engine.introspector,
engine.traced,
TrackedMut::reborrow_mut(&mut engine.sink),
engine.route.track(),
children,
locator.track(),
initial,
)
}
#[comemo::memoize]
#[allow(clippy::too_many_arguments)]
fn layout_page_run_impl(
world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>,
traced: Tracked<Traced>,
sink: TrackedMut<Sink>,
route: Tracked<Route>,
children: &[Pair],
locator: Tracked<Locator>,
initial: StyleChain,
) -> SourceResult<Vec<LayoutedPage>> {
let link = LocatorLink::new(locator);
let mut locator = Locator::link(&link).split();
let mut engine = Engine {
world,
introspector,
traced,
sink,
route: Route::extend(route),
};
let styles = determine_page_styles(children, initial);
let styles = StyleChain::new(&styles);
let width = PageElem::width_in(styles).unwrap_or(Abs::inf());
let height = PageElem::height_in(styles).unwrap_or(Abs::inf());
let mut size = Size::new(width, height);
if PageElem::flipped_in(styles) {
std::mem::swap(&mut size.x, &mut size.y);
}
let mut min = width.min(height);
if !min.is_finite() {
min = Paper::A4.width();
}
let default = Rel::<Length>::from((2.5 / 21.0) * min);
let margin = PageElem::margin_in(styles);
let two_sided = margin.two_sided.unwrap_or(false);
let margin = margin
.sides
.map(|side| side.and_then(Smart::custom).unwrap_or(default))
.resolve(styles)
.relative_to(size);
let fill = PageElem::fill_in(styles);
let foreground = PageElem::foreground_in(styles);
let background = PageElem::background_in(styles);
let header_ascent = PageElem::header_ascent_in(styles).relative_to(margin.top);
let footer_descent = PageElem::footer_descent_in(styles).relative_to(margin.bottom);
let numbering = PageElem::numbering_in(styles);
let number_align = PageElem::number_align_in(styles);
let binding =
PageElem::binding_in(styles).unwrap_or_else(|| match TextElem::dir_in(styles) {
Dir::LTR => Binding::Left,
_ => Binding::Right,
});
let numbering_marginal = numbering.as_ref().map(|numbering| {
let both = match numbering {
Numbering::Pattern(pattern) => pattern.pieces() >= 2,
Numbering::Func(_) => true,
};
let mut counter = CounterDisplayElem::new(
Counter::new(CounterKey::Page),
Smart::Custom(numbering.clone()),
both,
)
.pack();
if let Some(x) = number_align.x() {
counter = counter.aligned(x.into());
}
counter
});
let header = PageElem::header_in(styles);
let footer = PageElem::footer_in(styles);
let (header, footer) = if matches!(number_align.y(), Some(OuterVAlignment::Top)) {
(header.as_ref().unwrap_or(&numbering_marginal), footer.as_ref().unwrap_or(&None))
} else {
(header.as_ref().unwrap_or(&None), footer.as_ref().unwrap_or(&numbering_marginal))
};
let area = size - margin.sum_by_axis();
let fragment = layout_flow(
&mut engine,
children,
&mut locator,
styles,
Regions::repeat(area, area.map(Abs::is_finite)),
PageElem::columns_in(styles),
ColumnsElem::gutter_in(styles),
true,
)?;
let mut layout_marginal = |content: &Option<Content>, area, align| {
let Some(content) = content else { return Ok(None) };
let aligned = content.clone().styled(AlignElem::set_alignment(align));
layout_frame(
&mut engine,
&aligned,
locator.next(&content.span()),
styles,
Region::new(area, Axes::splat(true)),
)
.map(Some)
};
let mut layouted = Vec::with_capacity(fragment.len());
for inner in fragment {
let header_size = Size::new(inner.width(), margin.top - header_ascent);
let footer_size = Size::new(inner.width(), margin.bottom - footer_descent);
let full_size = inner.size() + margin.sum_by_axis();
let mid = HAlignment::Center + VAlignment::Horizon;
layouted.push(LayoutedPage {
inner,
fill: fill.clone(),
numbering: numbering.clone(),
header: layout_marginal(header, header_size, Alignment::BOTTOM)?,
footer: layout_marginal(footer, footer_size, Alignment::TOP)?,
background: layout_marginal(background, full_size, mid)?,
foreground: layout_marginal(foreground, full_size, mid)?,
margin,
binding,
two_sided,
});
}
Ok(layouted)
}
fn determine_page_styles(children: &[Pair], initial: StyleChain) -> Styles {
let tagless = children.iter().filter(|(c, _)| !c.is::<TagElem>()).map(|&(_, s)| s);
let base = StyleChain::trunk(tagless).unwrap_or(initial).to_map();
let trunk_len = initial
.to_map()
.as_slice()
.iter()
.zip(base.as_slice())
.take_while(|&(a, b)| a == b)
.count();
base.into_iter()
.enumerate()
.filter(|(i, style)| {
let initial = *i < trunk_len;
style.outside() && (initial || style.liftable())
})
.map(|(_, style)| style)
.collect()
}