use crate::prelude::*; use crate::text::TextElem; /// Separate a region into multiple equally sized columns. /// /// The `column` function allows to separate the interior of any container into /// multiple columns. It will not equalize the height of the columns, instead, /// the columns will take up the height of their container or the remaining /// height on the page. The columns function can break across pages if /// necessary. /// /// ## Example { #example } /// ```example /// = Towards Advanced Deep Learning /// /// #box(height: 68pt, /// columns(2, gutter: 11pt)[ /// #set par(justify: true) /// This research was funded by the /// National Academy of Sciences. /// NAoS provided support for field /// tests and interviews with a /// grant of up to USD 40.000 for a /// period of 6 months. /// ] /// ) /// /// In recent years, deep learning has /// increasingly been used to solve a /// variety of problems. /// ``` /// /// Display: Columns /// Category: layout #[element(Layout)] pub struct ColumnsElem { /// The number of columns. #[positional] #[default(NonZeroUsize::new(2).unwrap())] pub count: NonZeroUsize, /// The size of the gutter space between each column. #[resolve] #[default(Ratio::new(0.04).into())] pub gutter: Rel, /// The content that should be layouted into the columns. #[required] pub body: Content, } impl Layout for ColumnsElem { #[tracing::instrument(name = "ColumnsElem::layout", skip_all)] fn layout( &self, vt: &mut Vt, styles: StyleChain, regions: Regions, ) -> SourceResult { let body = self.body(); // Separating the infinite space into infinite columns does not make // much sense. if !regions.size.x.is_finite() { return body.layout(vt, styles, regions); } // Determine the width of the gutter and each column. let columns = self.count(styles).get(); let gutter = self.gutter(styles).relative_to(regions.base().x); let width = (regions.size.x - gutter * (columns - 1) as f64) / columns as f64; let backlog: Vec<_> = std::iter::once(®ions.size.y) .chain(regions.backlog) .flat_map(|&height| std::iter::repeat(height).take(columns)) .skip(1) .collect(); // Create the pod regions. let pod = Regions { size: Size::new(width, regions.size.y), full: regions.full, backlog: &backlog, last: regions.last, expand: Axes::new(true, regions.expand.y), root: regions.root, }; // Layout the children. let mut frames = body.layout(vt, styles, pod)?.into_iter(); let mut finished = vec![]; let dir = TextElem::dir_in(styles); let total_regions = (frames.len() as f32 / columns as f32).ceil() as usize; // Stitch together the columns for each region. for region in regions.iter().take(total_regions) { // The height should be the parent height if we should expand. // Otherwise its the maximum column height for the frame. In that // case, the frame is first created with zero height and then // resized. let height = if regions.expand.y { region.y } else { Abs::zero() }; let mut output = Frame::new(Size::new(regions.size.x, height)); let mut cursor = Abs::zero(); for _ in 0..columns { let Some(frame) = frames.next() else { break }; if !regions.expand.y { output.size_mut().y.set_max(frame.height()); } let width = frame.width(); let x = if dir == Dir::LTR { cursor } else { regions.size.x - cursor - width }; output.push_frame(Point::with_x(x), frame); cursor += width + gutter; } finished.push(output); } Ok(Fragment::frames(finished)) } } /// A forced column break. /// /// The function will behave like a [page break]($func/pagebreak) when used in a /// single column layout or the last column on a page. Otherwise, content after /// the column break will be placed in the next column. /// /// ## Example { #example } /// ```example /// #set page(columns: 2) /// Preliminary findings from our /// ongoing research project have /// revealed a hitherto unknown /// phenomenon of extraordinary /// significance. /// /// #colbreak() /// Through rigorous experimentation /// and analysis, we have discovered /// a hitherto uncharacterized process /// that defies our current /// understanding of the fundamental /// laws of nature. /// ``` /// /// Display: Column Break /// Category: layout #[element(Behave)] pub struct ColbreakElem { /// If `{true}`, the column break is skipped if the current column is /// already empty. #[default(false)] pub weak: bool, } impl Behave for ColbreakElem { fn behaviour(&self) -> Behaviour { if self.weak(StyleChain::default()) { Behaviour::Weak(1) } else { Behaviour::Destructive } } }