mod collect;
mod compose;
mod distribute;
use std::collections::HashSet;
use std::num::NonZeroUsize;
use std::rc::Rc;
use bumpalo::Bump;
use comemo::{Track, Tracked, TrackedMut};
use ecow::EcoVec;
use self::collect::{
collect, Child, LineChild, MultiChild, MultiSpill, PlacedChild, SingleChild,
};
use self::compose::{compose, Composer};
use self::distribute::distribute;
use crate::diag::{bail, At, SourceDiagnostic, SourceResult};
use crate::engine::{Engine, Route, Sink, Traced};
use crate::foundations::{Content, Packed, Resolve, StyleChain};
use crate::introspection::{
Introspector, Location, Locator, LocatorLink, SplitLocator, Tag,
};
use crate::layout::{
Abs, Dir, Em, Fragment, Frame, PageElem, PlacementScope, Region, Regions, Rel, Size,
};
use crate::model::{FootnoteElem, FootnoteEntry, LineNumberingScope, ParLine};
use crate::realize::{realize, Arenas, Pair, RealizationKind};
use crate::text::TextElem;
use crate::utils::{NonZeroExt, Numeric};
use crate::World;
pub fn layout_fragment(
engine: &mut Engine,
content: &Content,
locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
layout_fragment_impl(
engine.world,
engine.introspector,
engine.traced,
TrackedMut::reborrow_mut(&mut engine.sink),
engine.route.track(),
content,
locator.track(),
styles,
regions,
NonZeroUsize::ONE,
Rel::zero(),
)
}
pub fn layout_fragment_with_columns(
engine: &mut Engine,
content: &Content,
locator: Locator,
styles: StyleChain,
regions: Regions,
count: NonZeroUsize,
gutter: Rel<Abs>,
) -> SourceResult<Fragment> {
layout_fragment_impl(
engine.world,
engine.introspector,
engine.traced,
TrackedMut::reborrow_mut(&mut engine.sink),
engine.route.track(),
content,
locator.track(),
styles,
regions,
count,
gutter,
)
}
pub fn layout_frame(
engine: &mut Engine,
content: &Content,
locator: Locator,
styles: StyleChain,
region: Region,
) -> SourceResult<Frame> {
layout_fragment(engine, content, locator, styles, region.into())
.map(Fragment::into_frame)
}
#[comemo::memoize]
#[allow(clippy::too_many_arguments)]
fn layout_fragment_impl(
world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>,
traced: Tracked<Traced>,
sink: TrackedMut<Sink>,
route: Tracked<Route>,
content: &Content,
locator: Tracked<Locator>,
styles: StyleChain,
regions: Regions,
columns: NonZeroUsize,
column_gutter: Rel<Abs>,
) -> SourceResult<Fragment> {
if !regions.size.x.is_finite() && regions.expand.x {
bail!(content.span(), "cannot expand into infinite width");
}
if !regions.size.y.is_finite() && regions.expand.y {
bail!(content.span(), "cannot expand into infinite height");
}
let link = LocatorLink::new(locator);
let mut locator = Locator::link(&link).split();
let mut engine = Engine {
world,
introspector,
traced,
sink,
route: Route::extend(route),
};
engine.route.check_layout_depth().at(content.span())?;
let arenas = Arenas::default();
let children = realize(
RealizationKind::Container,
&mut engine,
&mut locator,
&arenas,
content,
styles,
)?;
layout_flow(
&mut engine,
&children,
&mut locator,
styles,
regions,
columns,
column_gutter,
false,
)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn layout_flow(
engine: &mut Engine,
children: &[Pair],
locator: &mut SplitLocator,
shared: StyleChain,
mut regions: Regions,
columns: NonZeroUsize,
column_gutter: Rel<Abs>,
root: bool,
) -> SourceResult<Fragment> {
let config = Config {
root,
shared,
columns: {
let mut count = columns.get();
if !regions.size.x.is_finite() {
count = 1;
}
let gutter = column_gutter.relative_to(regions.base().x);
let width = (regions.size.x - gutter * (count - 1) as f64) / count as f64;
let dir = TextElem::dir_in(shared);
ColumnConfig { count, width, gutter, dir }
},
footnote: FootnoteConfig {
separator: FootnoteEntry::separator_in(shared),
clearance: FootnoteEntry::clearance_in(shared),
gap: FootnoteEntry::gap_in(shared),
expand: regions.expand.x,
},
line_numbers: root.then(|| LineNumberConfig {
scope: ParLine::numbering_scope_in(shared),
default_clearance: {
let width = if PageElem::flipped_in(shared) {
PageElem::height_in(shared)
} else {
PageElem::width_in(shared)
};
(0.026 * width.unwrap_or_default())
.clamp(Em::new(0.75).resolve(shared), Em::new(2.5).resolve(shared))
},
}),
};
let bump = Bump::new();
let children = collect(
engine,
&bump,
children,
locator.next(&()),
Size::new(config.columns.width, regions.full),
regions.expand.x,
)?;
let mut work = Work::new(&children);
let mut finished = vec![];
loop {
let frame = compose(engine, &mut work, &config, locator.next(&()), regions)?;
finished.push(frame);
if work.done() && (!regions.expand.y || regions.backlog.is_empty()) {
break;
}
regions.next();
}
Ok(Fragment::frames(finished))
}
#[derive(Clone)]
struct Work<'a, 'b> {
children: &'b [Child<'a>],
spill: Option<MultiSpill<'a, 'b>>,
floats: EcoVec<&'b PlacedChild<'a>>,
footnotes: EcoVec<Packed<FootnoteElem>>,
footnote_spill: Option<std::vec::IntoIter<Frame>>,
tags: EcoVec<&'a Tag>,
skips: Rc<HashSet<Location>>,
}
impl<'a, 'b> Work<'a, 'b> {
fn new(children: &'b [Child<'a>]) -> Self {
Self {
children,
spill: None,
floats: EcoVec::new(),
footnotes: EcoVec::new(),
footnote_spill: None,
tags: EcoVec::new(),
skips: Rc::new(HashSet::new()),
}
}
fn head(&self) -> Option<&'b Child<'a>> {
self.children.first()
}
fn advance(&mut self) {
self.children = &self.children[1..];
}
fn done(&self) -> bool {
self.children.is_empty()
&& self.spill.is_none()
&& self.floats.is_empty()
&& self.footnote_spill.is_none()
&& self.footnotes.is_empty()
}
fn extend_skips(&mut self, skips: &[Location]) {
if !skips.is_empty() {
Rc::make_mut(&mut self.skips).extend(skips.iter().copied());
}
}
}
struct Config<'x> {
root: bool,
shared: StyleChain<'x>,
columns: ColumnConfig,
footnote: FootnoteConfig,
line_numbers: Option<LineNumberConfig>,
}
struct FootnoteConfig {
separator: Content,
clearance: Abs,
gap: Abs,
expand: bool,
}
struct ColumnConfig {
count: usize,
width: Abs,
gutter: Abs,
dir: Dir,
}
struct LineNumberConfig {
scope: LineNumberingScope,
default_clearance: Abs,
}
type FlowResult<T> = Result<T, Stop>;
enum Stop {
Finish(bool),
Relayout(PlacementScope),
Error(EcoVec<SourceDiagnostic>),
}
impl From<EcoVec<SourceDiagnostic>> for Stop {
fn from(error: EcoVec<SourceDiagnostic>) -> Self {
Stop::Error(error)
}
}