diff options
Diffstat (limited to 'src/library/layout')
| -rw-r--r-- | src/library/layout/align.rs | 62 | ||||
| -rw-r--r-- | src/library/layout/columns.rs | 117 | ||||
| -rw-r--r-- | src/library/layout/container.rs | 80 | ||||
| -rw-r--r-- | src/library/layout/flow.rs | 267 | ||||
| -rw-r--r-- | src/library/layout/grid.rs | 593 | ||||
| -rw-r--r-- | src/library/layout/mod.rs | 814 | ||||
| -rw-r--r-- | src/library/layout/pad.rs | 83 | ||||
| -rw-r--r-- | src/library/layout/page.rs | 421 | ||||
| -rw-r--r-- | src/library/layout/place.rs | 56 | ||||
| -rw-r--r-- | src/library/layout/spacing.rs | 100 | ||||
| -rw-r--r-- | src/library/layout/stack.rs | 321 | ||||
| -rw-r--r-- | src/library/layout/transform.rs | 116 |
12 files changed, 0 insertions, 3030 deletions
diff --git a/src/library/layout/align.rs b/src/library/layout/align.rs deleted file mode 100644 index 2ee565cc..00000000 --- a/src/library/layout/align.rs +++ /dev/null @@ -1,62 +0,0 @@ -use crate::library::prelude::*; -use crate::library::text::{HorizontalAlign, ParNode}; - -/// Align content along the layouting axes. -#[derive(Debug, Hash)] -pub struct AlignNode { - /// How to align the content horizontally and vertically. - pub aligns: Axes<Option<RawAlign>>, - /// The content to be aligned. - pub child: Content, -} - -#[node(LayoutBlock)] -impl AlignNode { - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - let aligns: Axes<Option<RawAlign>> = args.find()?.unwrap_or_default(); - let body: Content = args.expect("body")?; - - if let Axes { x: Some(x), y: None } = aligns { - if !body.has::<dyn LayoutBlock>() { - return Ok(body.styled(ParNode::ALIGN, HorizontalAlign(x))); - } - } - - Ok(body.aligned(aligns)) - } -} - -impl LayoutBlock for AlignNode { - fn layout_block( - &self, - world: Tracked<dyn World>, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult<Vec<Frame>> { - // The child only needs to expand along an axis if there's no alignment. - let mut pod = regions.clone(); - pod.expand &= self.aligns.as_ref().map(Option::is_none); - - // Align paragraphs inside the child. - let mut passed = StyleMap::new(); - if let Some(align) = self.aligns.x { - passed.set(ParNode::ALIGN, HorizontalAlign(align)); - } - - // Layout the child. - let mut frames = self.child.layout_block(world, &pod, passed.chain(&styles))?; - for (region, frame) in regions.iter().zip(&mut frames) { - // Align in the target size. The target size depends on whether we - // should expand. - let target = regions.expand.select(region, frame.size()); - let aligns = self - .aligns - .map(|align| align.resolve(styles)) - .unwrap_or(Axes::new(Align::Left, Align::Top)); - - frame.resize(target, aligns); - } - - Ok(frames) - } -} diff --git a/src/library/layout/columns.rs b/src/library/layout/columns.rs deleted file mode 100644 index df259eab..00000000 --- a/src/library/layout/columns.rs +++ /dev/null @@ -1,117 +0,0 @@ -use crate::library::prelude::*; -use crate::library::text::TextNode; - -/// Separate a region into multiple equally sized columns. -#[derive(Debug, Hash)] -pub struct ColumnsNode { - /// How many columns there should be. - pub columns: NonZeroUsize, - /// The child to be layouted into the columns. Most likely, this should be a - /// flow or stack node. - pub child: Content, -} - -#[node(LayoutBlock)] -impl ColumnsNode { - /// The size of the gutter space between each column. - #[property(resolve)] - pub const GUTTER: Rel<Length> = Ratio::new(0.04).into(); - - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Self { - columns: args.expect("column count")?, - child: args.expect("body")?, - } - .pack()) - } -} - -impl LayoutBlock for ColumnsNode { - fn layout_block( - &self, - world: Tracked<dyn World>, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult<Vec<Frame>> { - // Separating the infinite space into infinite columns does not make - // much sense. - if !regions.first.x.is_finite() { - return self.child.layout_block(world, regions, styles); - } - - // Determine the width of the gutter and each column. - let columns = self.columns.get(); - let gutter = styles.get(Self::GUTTER).relative_to(regions.base.x); - let width = (regions.first.x - gutter * (columns - 1) as f64) / columns as f64; - - // Create the pod regions. - let pod = Regions { - first: Size::new(width, regions.first.y), - base: Size::new(width, regions.base.y), - backlog: std::iter::once(®ions.first.y) - .chain(regions.backlog.as_slice()) - .flat_map(|&height| std::iter::repeat(height).take(columns)) - .skip(1) - .collect(), - last: regions.last, - expand: Axes::new(true, regions.expand.y), - }; - - // Layout the children. - let mut frames = self.child.layout_block(world, &pod, styles)?.into_iter(); - let mut finished = vec![]; - - let dir = styles.get(TextNode::DIR); - 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.first.x, height)); - let mut cursor = Abs::zero(); - - for _ in 0 .. columns { - let frame = match frames.next() { - Some(frame) => frame, - None => break, - }; - - if !regions.expand.y { - output.size_mut().y.set_max(frame.height()); - } - - let width = frame.width(); - let x = if dir.is_positive() { - cursor - } else { - regions.first.x - cursor - width - }; - - output.push_frame(Point::with_x(x), frame); - cursor += width + gutter; - } - - finished.push(output); - } - - Ok(finished) - } -} - -/// A column break. -#[derive(Debug, Clone, Hash)] -pub struct ColbreakNode { - pub weak: bool, -} - -#[node] -impl ColbreakNode { - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - let weak = args.named("weak")?.unwrap_or(false); - Ok(Self { weak }.pack()) - } -} diff --git a/src/library/layout/container.rs b/src/library/layout/container.rs deleted file mode 100644 index 023809d0..00000000 --- a/src/library/layout/container.rs +++ /dev/null @@ -1,80 +0,0 @@ -use crate::library::prelude::*; - -/// An inline-level container that sizes content. -#[derive(Debug, Clone, Hash)] -pub struct BoxNode { - /// How to size the content horizontally and vertically. - pub sizing: Axes<Option<Rel<Length>>>, - /// The content to be sized. - pub child: Content, -} - -#[node(LayoutInline)] -impl BoxNode { - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - let width = args.named("width")?; - let height = args.named("height")?; - let body = args.eat::<Content>()?.unwrap_or_default(); - Ok(body.boxed(Axes::new(width, height))) - } -} - -impl LayoutInline for BoxNode { - fn layout_inline( - &self, - world: Tracked<dyn World>, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult<Vec<Frame>> { - // The "pod" is the region into which the child will be layouted. - let pod = { - // Resolve the sizing to a concrete size. - let size = self - .sizing - .resolve(styles) - .zip(regions.base) - .map(|(s, b)| s.map(|v| v.relative_to(b))) - .unwrap_or(regions.first); - - // Select the appropriate base and expansion for the child depending - // on whether it is automatically or relatively sized. - let is_auto = self.sizing.as_ref().map(Option::is_none); - let base = is_auto.select(regions.base, size); - let expand = regions.expand | !is_auto; - - Regions::one(size, base, expand) - }; - - // Layout the child. - let mut frames = self.child.layout_inline(world, &pod, styles)?; - - // Ensure frame size matches regions size if expansion is on. - let frame = &mut frames[0]; - let target = regions.expand.select(regions.first, frame.size()); - frame.resize(target, Align::LEFT_TOP); - - Ok(frames) - } -} - -/// A block-level container that places content into a separate flow. -#[derive(Debug, Clone, Hash)] -pub struct BlockNode(pub Content); - -#[node(LayoutBlock)] -impl BlockNode { - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Self(args.eat()?.unwrap_or_default()).pack()) - } -} - -impl LayoutBlock for BlockNode { - fn layout_block( - &self, - world: Tracked<dyn World>, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult<Vec<Frame>> { - self.0.layout_block(world, regions, styles) - } -} diff --git a/src/library/layout/flow.rs b/src/library/layout/flow.rs deleted file mode 100644 index f4d18699..00000000 --- a/src/library/layout/flow.rs +++ /dev/null @@ -1,267 +0,0 @@ -use std::cmp::Ordering; - -use super::{AlignNode, PlaceNode, Spacing}; -use crate::library::prelude::*; -use crate::library::text::ParNode; - -/// Arrange spacing, paragraphs and block-level nodes into a flow. -/// -/// This node is reponsible for layouting both the top-level content flow and -/// the contents of boxes. -#[derive(Hash)] -pub struct FlowNode(pub StyleVec<FlowChild>); - -/// A child of a flow node. -#[derive(Hash, PartialEq)] -pub enum FlowChild { - /// Vertical spacing between other children. - Spacing(Spacing), - /// Arbitrary block-level content. - Block(Content), - /// A column / region break. - Colbreak, -} - -#[node(LayoutBlock)] -impl FlowNode {} - -impl LayoutBlock for FlowNode { - fn layout_block( - &self, - world: Tracked<dyn World>, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult<Vec<Frame>> { - let mut layouter = FlowLayouter::new(regions); - - for (child, map) in self.0.iter() { - let styles = map.chain(&styles); - match child { - FlowChild::Spacing(kind) => { - layouter.layout_spacing(*kind, styles); - } - FlowChild::Block(block) => { - layouter.layout_block(world, block, styles)?; - } - FlowChild::Colbreak => { - layouter.finish_region(); - } - } - } - - Ok(layouter.finish()) - } -} - -impl Debug for FlowNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("Flow ")?; - self.0.fmt(f) - } -} - -impl Debug for FlowChild { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Spacing(kind) => write!(f, "{:?}", kind), - Self::Block(block) => block.fmt(f), - Self::Colbreak => f.pad("Colbreak"), - } - } -} - -impl PartialOrd for FlowChild { - fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - match (self, other) { - (Self::Spacing(a), Self::Spacing(b)) => a.partial_cmp(b), - _ => None, - } - } -} - -/// Performs flow layout. -pub struct FlowLayouter { - /// The regions to layout children into. - regions: Regions, - /// Whether the flow should expand to fill the region. - expand: Axes<bool>, - /// The full size of `regions.size` that was available before we started - /// subtracting. - full: Size, - /// The size used by the frames for the current region. - used: Size, - /// The sum of fractions in the current region. - fr: Fr, - /// Spacing and layouted blocks. - items: Vec<FlowItem>, - /// Finished frames for previous regions. - finished: Vec<Frame>, -} - -/// A prepared item in a flow layout. -enum FlowItem { - /// Absolute spacing between other items. - Absolute(Abs), - /// Fractional spacing between other items. - Fractional(Fr), - /// A frame for a layouted block and how to align it. - Frame(Frame, Axes<Align>), - /// An absolutely placed frame. - Placed(Frame), -} - -impl FlowLayouter { - /// Create a new flow layouter. - pub fn new(regions: &Regions) -> Self { - let expand = regions.expand; - let full = regions.first; - - // Disable vertical expansion for children. - let mut regions = regions.clone(); - regions.expand.y = false; - - Self { - regions, - expand, - full, - used: Size::zero(), - fr: Fr::zero(), - items: vec![], - finished: vec![], - } - } - - /// Layout spacing. - pub fn layout_spacing(&mut self, spacing: Spacing, styles: StyleChain) { - match spacing { - Spacing::Relative(v) => { - // Resolve the spacing and limit it to the remaining space. - let resolved = v.resolve(styles).relative_to(self.full.y); - let limited = resolved.min(self.regions.first.y); - self.regions.first.y -= limited; - self.used.y += limited; - self.items.push(FlowItem::Absolute(resolved)); - } - Spacing::Fractional(v) => { - self.items.push(FlowItem::Fractional(v)); - self.fr += v; - } - } - } - - /// Layout a block. - pub fn layout_block( - &mut self, - world: Tracked<dyn World>, - block: &Content, - styles: StyleChain, - ) -> SourceResult<()> { - // Don't even try layouting into a full region. - if self.regions.is_full() { - self.finish_region(); - } - - // Placed nodes that are out of flow produce placed items which aren't - // aligned later. - if let Some(placed) = block.downcast::<PlaceNode>() { - if placed.out_of_flow() { - let frame = block.layout_block(world, &self.regions, styles)?.remove(0); - self.items.push(FlowItem::Placed(frame)); - return Ok(()); - } - } - - // How to align the block. - let aligns = Axes::new( - // For non-expanding paragraphs it is crucial that we align the - // whole paragraph as it is itself aligned. - styles.get(ParNode::ALIGN), - // Vertical align node alignment is respected by the flow. - block - .downcast::<AlignNode>() - .and_then(|aligned| aligned.aligns.y) - .map(|align| align.resolve(styles)) - .unwrap_or(Align::Top), - ); - - let frames = block.layout_block(world, &self.regions, styles)?; - let len = frames.len(); - for (i, mut frame) in frames.into_iter().enumerate() { - // Set the generic block role. - frame.apply_role(Role::GenericBlock); - - // Grow our size, shrink the region and save the frame for later. - let size = frame.size(); - self.used.y += size.y; - self.used.x.set_max(size.x); - self.regions.first.y -= size.y; - self.items.push(FlowItem::Frame(frame, aligns)); - - if i + 1 < len { - self.finish_region(); - } - } - - Ok(()) - } - - /// Finish the frame for one region. - pub fn finish_region(&mut self) { - // Determine the size of the flow in this region dependening on whether - // the region expands. - let mut size = self.expand.select(self.full, self.used); - - // Account for fractional spacing in the size calculation. - let remaining = self.full.y - self.used.y; - if self.fr.get() > 0.0 && self.full.y.is_finite() { - self.used.y = self.full.y; - size.y = self.full.y; - } - - let mut output = Frame::new(size); - let mut offset = Abs::zero(); - let mut ruler = Align::Top; - - // Place all frames. - for item in self.items.drain(..) { - match item { - FlowItem::Absolute(v) => { - offset += v; - } - FlowItem::Fractional(v) => { - offset += v.share(self.fr, remaining); - } - FlowItem::Frame(frame, aligns) => { - ruler = ruler.max(aligns.y); - let x = aligns.x.position(size.x - frame.width()); - let y = offset + ruler.position(size.y - self.used.y); - let pos = Point::new(x, y); - offset += frame.height(); - output.push_frame(pos, frame); - } - FlowItem::Placed(frame) => { - output.push_frame(Point::zero(), frame); - } - } - } - - // Advance to the next region. - self.regions.next(); - self.full = self.regions.first; - self.used = Size::zero(); - self.fr = Fr::zero(); - self.finished.push(output); - } - - /// Finish layouting and return the resulting frames. - pub fn finish(mut self) -> Vec<Frame> { - if self.expand.y { - while self.regions.backlog.len() > 0 { - self.finish_region(); - } - } - - self.finish_region(); - self.finished - } -} diff --git a/src/library/layout/grid.rs b/src/library/layout/grid.rs deleted file mode 100644 index 1bb67691..00000000 --- a/src/library/layout/grid.rs +++ /dev/null @@ -1,593 +0,0 @@ -use crate::library::prelude::*; - -/// Arrange content in a grid. -#[derive(Debug, Hash)] -pub struct GridNode { - /// Defines sizing for content rows and columns. - pub tracks: Axes<Vec<TrackSizing>>, - /// Defines sizing of gutter rows and columns between content. - pub gutter: Axes<Vec<TrackSizing>>, - /// The content to be arranged in a grid. - pub cells: Vec<Content>, -} - -#[node(LayoutBlock)] -impl GridNode { - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - let columns = args.named("columns")?.unwrap_or_default(); - let rows = args.named("rows")?.unwrap_or_default(); - let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default(); - let column_gutter = args.named("column-gutter")?; - let row_gutter = args.named("row-gutter")?; - Ok(Self { - tracks: Axes::new(columns, rows), - gutter: Axes::new( - column_gutter.unwrap_or_else(|| base_gutter.clone()), - row_gutter.unwrap_or(base_gutter), - ), - cells: args.all()?, - } - .pack()) - } -} - -impl LayoutBlock for GridNode { - fn layout_block( - &self, - world: Tracked<dyn World>, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult<Vec<Frame>> { - // Prepare grid layout by unifying content and gutter tracks. - let layouter = GridLayouter::new( - world, - self.tracks.as_deref(), - self.gutter.as_deref(), - &self.cells, - regions, - styles, - ); - - // Measure the columns and layout the grid row-by-row. - layouter.layout() - } -} - -/// Defines how to size a grid cell along an axis. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum TrackSizing { - /// A track that fits its cell's contents. - Auto, - /// A track size specified in absolute terms and relative to the parent's - /// size. - Relative(Rel<Length>), - /// A track size specified as a fraction of the remaining free space in the - /// parent. - Fractional(Fr), -} - -castable! { - Vec<TrackSizing>, - Expected: "integer, auto, relative length, fraction, or array of the latter three", - Value::Auto => vec![TrackSizing::Auto], - Value::Length(v) => vec![TrackSizing::Relative(v.into())], - Value::Ratio(v) => vec![TrackSizing::Relative(v.into())], - Value::Relative(v) => vec![TrackSizing::Relative(v)], - Value::Fraction(v) => vec![TrackSizing::Fractional(v)], - Value::Int(v) => vec![TrackSizing::Auto; Value::Int(v).cast::<NonZeroUsize>()?.get()], - Value::Array(values) => values - .into_iter() - .filter_map(|v| v.cast().ok()) - .collect(), -} - -castable! { - TrackSizing, - Expected: "auto, relative length, or fraction", - Value::Auto => Self::Auto, - Value::Length(v) => Self::Relative(v.into()), - Value::Ratio(v) => Self::Relative(v.into()), - Value::Relative(v) => Self::Relative(v), - Value::Fraction(v) => Self::Fractional(v), -} - -/// Performs grid layout. -pub struct GridLayouter<'a> { - /// The core context. - world: Tracked<'a, dyn World>, - /// The grid cells. - cells: &'a [Content], - /// The column tracks including gutter tracks. - cols: Vec<TrackSizing>, - /// The row tracks including gutter tracks. - rows: Vec<TrackSizing>, - /// The regions to layout children into. - regions: Regions, - /// The inherited styles. - styles: StyleChain<'a>, - /// Resolved column sizes. - rcols: Vec<Abs>, - /// Rows in the current region. - lrows: Vec<Row>, - /// The full height of the current region. - full: Abs, - /// The used-up size of the current region. The horizontal size is - /// determined once after columns are resolved and not touched again. - used: Size, - /// The sum of fractions in the current region. - fr: Fr, - /// Frames for finished regions. - finished: Vec<Frame>, -} - -/// Produced by initial row layout, auto and relative rows are already finished, -/// fractional rows not yet. -enum Row { - /// Finished row frame of auto or relative row. - Frame(Frame), - /// Fractional row with y index. - Fr(Fr, usize), -} - -impl<'a> GridLayouter<'a> { - /// Create a new grid layouter. - /// - /// This prepares grid layout by unifying content and gutter tracks. - pub fn new( - world: Tracked<'a, dyn World>, - tracks: Axes<&[TrackSizing]>, - gutter: Axes<&[TrackSizing]>, - cells: &'a [Content], - regions: &Regions, - styles: StyleChain<'a>, - ) -> Self { - let mut cols = vec![]; - let mut rows = vec![]; - - // Number of content columns: Always at least one. - let c = tracks.x.len().max(1); - - // Number of content rows: At least as many as given, but also at least - // as many as needed to place each item. - let r = { - let len = cells.len(); - let given = tracks.y.len(); - let needed = len / c + (len % c).clamp(0, 1); - given.max(needed) - }; - - let auto = TrackSizing::Auto; - let zero = TrackSizing::Relative(Rel::zero()); - let get_or = |tracks: &[_], idx, default| { - tracks.get(idx).or(tracks.last()).copied().unwrap_or(default) - }; - - // Collect content and gutter columns. - for x in 0 .. c { - cols.push(get_or(tracks.x, x, auto)); - cols.push(get_or(gutter.x, x, zero)); - } - - // Collect content and gutter rows. - for y in 0 .. r { - rows.push(get_or(tracks.y, y, auto)); - rows.push(get_or(gutter.y, y, zero)); - } - - // Remove superfluous gutter tracks. - cols.pop(); - rows.pop(); - - let full = regions.first.y; - let rcols = vec![Abs::zero(); cols.len()]; - let lrows = vec![]; - - // We use the regions for auto row measurement. Since at that moment, - // columns are already sized, we can enable horizontal expansion. - let mut regions = regions.clone(); - regions.expand = Axes::new(true, false); - - Self { - world, - cells, - cols, - rows, - regions, - styles, - rcols, - lrows, - full, - used: Size::zero(), - fr: Fr::zero(), - finished: vec![], - } - } - - /// Determines the columns sizes and then layouts the grid row-by-row. - pub fn layout(mut self) -> SourceResult<Vec<Frame>> { - self.measure_columns()?; - - for y in 0 .. self.rows.len() { - // Skip to next region if current one is full, but only for content - // rows, not for gutter rows. - if y % 2 == 0 && self.regions.is_full() { - self.finish_region()?; - } - - match self.rows[y] { - TrackSizing::Auto => self.layout_auto_row(y)?, - TrackSizing::Relative(v) => self.layout_relative_row(v, y)?, - TrackSizing::Fractional(v) => { - self.lrows.push(Row::Fr(v, y)); - self.fr += v; - } - } - } - - self.finish_region()?; - Ok(self.finished) - } - - /// Determine all column sizes. - fn measure_columns(&mut self) -> SourceResult<()> { - // Sum of sizes of resolved relative tracks. - let mut rel = Abs::zero(); - - // Sum of fractions of all fractional tracks. - let mut fr = Fr::zero(); - - // Resolve the size of all relative columns and compute the sum of all - // fractional tracks. - for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { - match col { - TrackSizing::Auto => {} - TrackSizing::Relative(v) => { - let resolved = - v.resolve(self.styles).relative_to(self.regions.base.x); - *rcol = resolved; - rel += resolved; - } - TrackSizing::Fractional(v) => fr += v, - } - } - - // Size that is not used by fixed-size columns. - let available = self.regions.first.x - rel; - if available >= Abs::zero() { - // Determine size of auto columns. - let (auto, count) = self.measure_auto_columns(available)?; - - // If there is remaining space, distribute it to fractional columns, - // otherwise shrink auto columns. - let remaining = available - auto; - if remaining >= Abs::zero() { - if !fr.is_zero() { - self.grow_fractional_columns(remaining, fr); - } - } else { - self.shrink_auto_columns(available, count); - } - } - - // Sum up the resolved column sizes once here. - self.used.x = self.rcols.iter().sum(); - - Ok(()) - } - - /// Measure the size that is available to auto columns. - fn measure_auto_columns(&mut self, available: Abs) -> SourceResult<(Abs, usize)> { - let mut auto = Abs::zero(); - let mut count = 0; - - // Determine size of auto columns by laying out all cells in those - // columns, measuring them and finding the largest one. - for (x, &col) in self.cols.iter().enumerate() { - if col != TrackSizing::Auto { - continue; - } - - let mut resolved = Abs::zero(); - for y in 0 .. self.rows.len() { - if let Some(cell) = self.cell(x, y) { - let size = Size::new(available, self.regions.base.y); - let mut pod = - Regions::one(size, self.regions.base, Axes::splat(false)); - - // For relative rows, we can already resolve the correct - // base, for auto it's already correct and for fr we could - // only guess anyway. - if let TrackSizing::Relative(v) = self.rows[y] { - pod.base.y = - v.resolve(self.styles).relative_to(self.regions.base.y); - } - - let frame = - cell.layout_block(self.world, &pod, self.styles)?.remove(0); - resolved.set_max(frame.width()); - } - } - - self.rcols[x] = resolved; - auto += resolved; - count += 1; - } - - Ok((auto, count)) - } - - /// Distribute remaining space to fractional columns. - fn grow_fractional_columns(&mut self, remaining: Abs, fr: Fr) { - for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { - if let TrackSizing::Fractional(v) = col { - *rcol = v.share(fr, remaining); - } - } - } - - /// Redistribute space to auto columns so that each gets a fair share. - fn shrink_auto_columns(&mut self, available: Abs, count: usize) { - // The fair share each auto column may have. - let fair = available / count as f64; - - // The number of overlarge auto columns and the space that will be - // equally redistributed to them. - let mut overlarge: usize = 0; - let mut redistribute = available; - - // Find out the number of and space used by overlarge auto columns. - for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { - if col == TrackSizing::Auto { - if *rcol > fair { - overlarge += 1; - } else { - redistribute -= *rcol; - } - } - } - - // Redistribute the space equally. - let share = redistribute / overlarge as f64; - for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { - if col == TrackSizing::Auto && *rcol > fair { - *rcol = share; - } - } - } - - /// Layout a row with automatic height. Such a row may break across multiple - /// regions. - fn layout_auto_row(&mut self, y: usize) -> SourceResult<()> { - let mut resolved: Vec<Abs> = vec![]; - - // Determine the size for each region of the row. - for (x, &rcol) in self.rcols.iter().enumerate() { - if let Some(cell) = self.cell(x, y) { - let mut pod = self.regions.clone(); - pod.first.x = rcol; - pod.base.x = rcol; - - // All widths should be `rcol` except the base for auto columns. - if self.cols[x] == TrackSizing::Auto { - pod.base.x = self.regions.base.x; - } - - let mut sizes = cell - .layout_block(self.world, &pod, self.styles)? - .into_iter() - .map(|frame| frame.height()); - - // For each region, we want to know the maximum height any - // column requires. - for (target, size) in resolved.iter_mut().zip(&mut sizes) { - target.set_max(size); - } - - // New heights are maximal by virtue of being new. Note that - // this extend only uses the rest of the sizes iterator. - resolved.extend(sizes); - } - } - - // Nothing to layout. - if resolved.is_empty() { - return Ok(()); - } - - // Layout into a single region. - if let &[first] = resolved.as_slice() { - let frame = self.layout_single_row(first, y)?; - self.push_row(frame); - return Ok(()); - } - - // Expand all but the last region if the space is not - // eaten up by any fr rows. - if self.fr.is_zero() { - let len = resolved.len(); - for (region, target) in self.regions.iter().zip(&mut resolved[.. len - 1]) { - target.set_max(region.y); - } - } - - // Layout into multiple regions. - let frames = self.layout_multi_row(&resolved, y)?; - let len = frames.len(); - for (i, frame) in frames.into_iter().enumerate() { - self.push_row(frame); - if i + 1 < len { - self.finish_region()?; - } - } - - Ok(()) - } - - /// Layout a row with relative height. Such a row cannot break across - /// multiple regions, but it may force a region break. - fn layout_relative_row(&mut self, v: Rel<Length>, y: usize) -> SourceResult<()> { - let resolved = v.resolve(self.styles).relative_to(self.regions.base.y); - let frame = self.layout_single_row(resolved, y)?; - - // Skip to fitting region. - let height = frame.height(); - while !self.regions.first.y.fits(height) && !self.regions.in_last() { - self.finish_region()?; - - // Don't skip multiple regions for gutter and don't push a row. - if y % 2 == 1 { - return Ok(()); - } - } - - self.push_row(frame); - - Ok(()) - } - - /// Layout a row with fixed height and return its frame. - fn layout_single_row(&mut self, height: Abs, y: usize) -> SourceResult<Frame> { - let mut output = Frame::new(Size::new(self.used.x, height)); - - let mut pos = Point::zero(); - - for (x, &rcol) in self.rcols.iter().enumerate() { - if let Some(cell) = self.cell(x, y) { - let size = Size::new(rcol, height); - - // Set the base to the region's base for auto rows and to the - // size for relative and fractional rows. - let base = Axes::new(self.cols[x], self.rows[y]) - .map(|s| s == TrackSizing::Auto) - .select(self.regions.base, size); - - let pod = Regions::one(size, base, Axes::splat(true)); - let frame = cell.layout_block(self.world, &pod, self.styles)?.remove(0); - match frame.role() { - Some(Role::ListLabel | Role::ListItemBody) => { - output.apply_role(Role::ListItem) - } - Some(Role::TableCell) => output.apply_role(Role::TableRow), - _ => {} - } - - output.push_frame(pos, frame); - } - - pos.x += rcol; - } - - Ok(output) - } - - /// Layout a row spanning multiple regions. - fn layout_multi_row( - &mut self, - heights: &[Abs], - y: usize, - ) -> SourceResult<Vec<Frame>> { - // Prepare frames. - let mut outputs: Vec<_> = heights - .iter() - .map(|&h| Frame::new(Size::new(self.used.x, h))) - .collect(); - - // Prepare regions. - let size = Size::new(self.used.x, heights[0]); - let mut pod = Regions::one(size, self.regions.base, Axes::splat(true)); - pod.backlog = heights[1 ..].to_vec(); - - // Layout the row. - let mut pos = Point::zero(); - for (x, &rcol) in self.rcols.iter().enumerate() { - if let Some(cell) = self.cell(x, y) { - pod.first.x = rcol; - pod.base.x = rcol; - - // All widths should be `rcol` except the base for auto columns. - if self.cols[x] == TrackSizing::Auto { - pod.base.x = self.regions.base.x; - } - - // Push the layouted frames into the individual output frames. - let frames = cell.layout_block(self.world, &pod, self.styles)?; - for (output, frame) in outputs.iter_mut().zip(frames) { - match frame.role() { - Some(Role::ListLabel | Role::ListItemBody) => { - output.apply_role(Role::ListItem) - } - Some(Role::TableCell) => output.apply_role(Role::TableRow), - _ => {} - } - output.push_frame(pos, frame); - } - } - - pos.x += rcol; - } - - Ok(outputs) - } - - /// Push a row frame into the current region. - fn push_row(&mut self, frame: Frame) { - self.regions.first.y -= frame.height(); - self.used.y += frame.height(); - self.lrows.push(Row::Frame(frame)); - } - - /// Finish rows for one region. - fn finish_region(&mut self) -> SourceResult<()> { - // Determine the size of the grid in this region, expanding fully if - // there are fr rows. - let mut size = self.used; - if self.fr.get() > 0.0 && self.full.is_finite() { - size.y = self.full; - } - - // The frame for the region. - let mut output = Frame::new(size); - let mut pos = Point::zero(); - - // Place finished rows and layout fractional rows. - for row in std::mem::take(&mut self.lrows) { - let frame = match row { - Row::Frame(frame) => frame, - Row::Fr(v, y) => { - let remaining = self.full - self.used.y; - let height = v.share(self.fr, remaining); - self.layout_single_row(height, y)? - } - }; - - let height = frame.height(); - output.push_frame(pos, frame); - pos.y += height; - } - - self.finished.push(output); - self.regions.next(); - self.full = self.regions.first.y; - self.used.y = Abs::zero(); - self.fr = Fr::zero(); - - Ok(()) - } - - /// Get the content of the cell in column `x` and row `y`. - /// - /// Returns `None` if it's a gutter cell. - #[track_caller] - fn cell(&self, x: usize, y: usize) -> Option<&'a Content> { - assert!(x < self.cols.len()); - assert!(y < self.rows.len()); - - // Even columns and rows are children, odd ones are gutter. - if x % 2 == 0 && y % 2 == 0 { - let c = 1 + self.cols.len() / 2; - self.cells.get((y / 2) * c + x / 2) - } else { - None - } - } -} diff --git a/src/library/layout/mod.rs b/src/library/layout/mod.rs deleted file mode 100644 index 000cb212..00000000 --- a/src/library/layout/mod.rs +++ /dev/null @@ -1,814 +0,0 @@ -//! Composable layouts. - -mod align; -mod columns; -mod container; -mod flow; -mod grid; -mod pad; -mod page; -mod place; -mod spacing; -mod stack; -mod transform; - -pub use align::*; -pub use columns::*; -pub use container::*; -pub use flow::*; -pub use grid::*; -pub use pad::*; -pub use page::*; -pub use place::*; -pub use spacing::*; -pub use stack::*; -pub use transform::*; - -use std::mem; - -use comemo::Tracked; -use typed_arena::Arena; - -use crate::diag::SourceResult; -use crate::frame::Frame; -use crate::geom::*; -use crate::library::structure::{DocNode, ListItem, ListNode, DESC, ENUM, LIST}; -use crate::library::text::{ - LinebreakNode, ParChild, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode, -}; -use crate::model::{ - capability, Barrier, Content, Interruption, Node, SequenceNode, Show, StyleChain, - StyleEntry, StyleMap, StyleVec, StyleVecBuilder, StyledNode, Target, -}; -use crate::World; - -/// The root-level layout. -#[capability] -pub trait Layout: 'static + Sync + Send { - /// Layout into one frame per page. - fn layout(&self, world: Tracked<dyn World>) -> SourceResult<Vec<Frame>>; -} - -impl Layout for Content { - #[comemo::memoize] - fn layout(&self, world: Tracked<dyn World>) -> SourceResult<Vec<Frame>> { - let styles = StyleChain::with_root(&world.config().styles); - let scratch = Scratch::default(); - - let mut builder = Builder::new(world, &scratch, true); - builder.accept(self, styles)?; - - let (doc, shared) = builder.into_doc(styles)?; - doc.layout(world, shared) - } -} - -/// Block-level layout. -#[capability] -pub trait LayoutBlock: 'static + Sync + Send { - /// Layout into one frame per region. - fn layout_block( - &self, - world: Tracked<dyn World>, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult<Vec<Frame>>; -} - -impl LayoutBlock for Content { - #[comemo::memoize] - fn layout_block( - &self, - world: Tracked<dyn World>, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult<Vec<Frame>> { - if let Some(node) = self.to::<dyn LayoutBlock>() { - let barrier = StyleEntry::Barrier(Barrier::new(self.id())); - let styles = barrier.chain(&styles); - return node.layout_block(world, regions, styles); - } - - let scratch = Scratch::default(); - let mut builder = Builder::new(world, &scratch, false); - builder.accept(self, styles)?; - let (flow, shared) = builder.into_flow(styles)?; - flow.layout_block(world, regions, shared) - } -} - -/// Inline-level layout. -#[capability] -pub trait LayoutInline: 'static + Sync + Send { - /// Layout into a single frame. - fn layout_inline( - &self, - world: Tracked<dyn World>, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult<Vec<Frame>>; -} - -impl LayoutInline for Content { - #[comemo::memoize] - fn layout_inline( - &self, - world: Tracked<dyn World>, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult<Vec<Frame>> { - if let Some(node) = self.to::<dyn LayoutInline>() { - let barrier = StyleEntry::Barrier(Barrier::new(self.id())); - let styles = barrier.chain(&styles); - return node.layout_inline(world, regions, styles); - } - - if let Some(node) = self.to::<dyn LayoutBlock>() { - let barrier = StyleEntry::Barrier(Barrier::new(self.id())); - let styles = barrier.chain(&styles); - return node.layout_block(world, regions, styles); - } - - let scratch = Scratch::default(); - let mut builder = Builder::new(world, &scratch, false); - builder.accept(self, styles)?; - let (flow, shared) = builder.into_flow(styles)?; - flow.layout_block(world, regions, shared) - } -} - -/// A sequence of regions to layout into. -#[derive(Debug, Clone, Hash)] -pub struct Regions { - /// The (remaining) size of the first region. - pub first: Size, - /// The base size for relative sizing. - pub base: Size, - /// The height of followup regions. The width is the same for all regions. - pub backlog: Vec<Abs>, - /// The height of the final region that is repeated once the backlog is - /// drained. The width is the same for all regions. - pub last: Option<Abs>, - /// Whether nodes should expand to fill the regions instead of shrinking to - /// fit the content. - pub expand: Axes<bool>, -} - -impl Regions { - /// Create a new region sequence with exactly one region. - pub fn one(size: Size, base: Size, expand: Axes<bool>) -> Self { - Self { - first: size, - base, - backlog: vec![], - last: None, - expand, - } - } - - /// Create a new sequence of same-size regions that repeats indefinitely. - pub fn repeat(size: Size, base: Size, expand: Axes<bool>) -> Self { - Self { - first: size, - base, - backlog: vec![], - last: Some(size.y), - expand, - } - } - - /// Create new regions where all sizes are mapped with `f`. - /// - /// Note that since all regions must have the same width, the width returned - /// by `f` is ignored for the backlog and the final region. - pub fn map<F>(&self, mut f: F) -> Self - where - F: FnMut(Size) -> Size, - { - let x = self.first.x; - Self { - first: f(self.first), - base: f(self.base), - backlog: self.backlog.iter().map(|&y| f(Size::new(x, y)).y).collect(), - last: self.last.map(|y| f(Size::new(x, y)).y), - expand: self.expand, - } - } - - /// Whether the first region is full and a region break is called for. - pub fn is_full(&self) -> bool { - Abs::zero().fits(self.first.y) && !self.in_last() - } - - /// Whether the first region is the last usable region. - /// - /// If this is true, calling `next()` will have no effect. - pub fn in_last(&self) -> bool { - self.backlog.len() == 0 && self.last.map_or(true, |height| self.first.y == height) - } - - /// Advance to the next region if there is any. - pub fn next(&mut self) { - if let Some(height) = (!self.backlog.is_empty()) - .then(|| self.backlog.remove(0)) - .or(self.last) - { - self.first.y = height; - self.base.y = height; - } - } - - /// An iterator that returns the sizes of the first and all following - /// regions, equivalently to what would be produced by calling - /// [`next()`](Self::next) repeatedly until all regions are exhausted. - /// This iterater may be infinite. - pub fn iter(&self) -> impl Iterator<Item = Size> + '_ { - let first = std::iter::once(self.first); - let backlog = self.backlog.iter(); - let last = self.last.iter().cycle(); - first.chain(backlog.chain(last).map(|&h| Size::new(self.first.x, h))) - } -} - -/// Builds a document or a flow node from content. -struct Builder<'a> { - /// The core context. - world: Tracked<'a, dyn World>, - /// Scratch arenas for building. - scratch: &'a Scratch<'a>, - /// The current document building state. - doc: Option<DocBuilder<'a>>, - /// The current flow building state. - flow: FlowBuilder<'a>, - /// The current paragraph building state. - par: ParBuilder<'a>, - /// The current list building state. - list: ListBuilder<'a>, -} - -/// Temporary storage arenas for building. -#[derive(Default)] -struct Scratch<'a> { - /// An arena where intermediate style chains are stored. - styles: Arena<StyleChain<'a>>, - /// An arena where intermediate content resulting from show rules is stored. - templates: Arena<Content>, -} - -impl<'a> Builder<'a> { - pub fn new( - world: Tracked<'a, dyn World>, - scratch: &'a Scratch<'a>, - top: bool, - ) -> Self { - Self { - world, - scratch, - doc: top.then(|| DocBuilder::default()), - flow: FlowBuilder::default(), - par: ParBuilder::default(), - list: ListBuilder::default(), - } - } - - pub fn into_doc( - mut self, - styles: StyleChain<'a>, - ) -> SourceResult<(DocNode, StyleChain<'a>)> { - self.interrupt(Interruption::Page, styles, true)?; - let (pages, shared) = self.doc.unwrap().pages.finish(); - Ok((DocNode(pages), shared)) - } - - pub fn into_flow( - mut self, - styles: StyleChain<'a>, - ) -> SourceResult<(FlowNode, StyleChain<'a>)> { - self.interrupt(Interruption::Par, styles, false)?; - let (children, shared) = self.flow.0.finish(); - Ok((FlowNode(children), shared)) - } - - pub fn accept( - &mut self, - content: &'a Content, - styles: StyleChain<'a>, - ) -> SourceResult<()> { - if let Some(text) = content.downcast::<TextNode>() { - if let Some(realized) = styles.apply(self.world, Target::Text(&text.0))? { - let stored = self.scratch.templates.alloc(realized); - return self.accept(stored, styles); - } - } else if let Some(styled) = content.downcast::<StyledNode>() { - return self.styled(styled, styles); - } else if let Some(seq) = content.downcast::<SequenceNode>() { - return self.sequence(seq, styles); - } else if content.has::<dyn Show>() { - if self.show(&content, styles)? { - return Ok(()); - } - } - - if self.list.accept(content, styles) { - return Ok(()); - } - - self.interrupt(Interruption::List, styles, false)?; - - if content.is::<ListItem>() { - self.list.accept(content, styles); - return Ok(()); - } - - if self.par.accept(content, styles) { - return Ok(()); - } - - self.interrupt(Interruption::Par, styles, false)?; - - if self.flow.accept(content, styles) { - return Ok(()); - } - - let keep = content - .downcast::<PagebreakNode>() - .map_or(false, |pagebreak| !pagebreak.weak); - self.interrupt(Interruption::Page, styles, keep)?; - - if let Some(doc) = &mut self.doc { - doc.accept(content, styles); - } - - // We might want to issue a warning or error for content that wasn't - // handled (e.g. a pagebreak in a flow building process). However, we - // don't have the spans here at the moment. - Ok(()) - } - - fn show( - &mut self, - content: &'a Content, - styles: StyleChain<'a>, - ) -> SourceResult<bool> { - if let Some(mut realized) = styles.apply(self.world, Target::Node(content))? { - let mut map = StyleMap::new(); - let barrier = Barrier::new(content.id()); - map.push(StyleEntry::Barrier(barrier)); - map.push(StyleEntry::Barrier(barrier)); - realized = realized.styled_with_map(map); - let stored = self.scratch.templates.alloc(realized); - self.accept(stored, styles)?; - Ok(true) - } else { - Ok(false) - } - } - - fn styled( - &mut self, - styled: &'a StyledNode, - styles: StyleChain<'a>, - ) -> SourceResult<()> { - let stored = self.scratch.styles.alloc(styles); - let styles = styled.map.chain(stored); - let intr = styled.map.interruption(); - - if let Some(intr) = intr { - self.interrupt(intr, styles, false)?; - } - - self.accept(&styled.sub, styles)?; - - if let Some(intr) = intr { - self.interrupt(intr, styles, true)?; - } - - Ok(()) - } - - fn interrupt( - &mut self, - intr: Interruption, - styles: StyleChain<'a>, - keep: bool, - ) -> SourceResult<()> { - if intr >= Interruption::List && !self.list.is_empty() { - mem::take(&mut self.list).finish(self)?; - } - - if intr >= Interruption::Par { - if !self.par.is_empty() { - mem::take(&mut self.par).finish(self); - } - } - - if intr >= Interruption::Page { - if let Some(doc) = &mut self.doc { - if !self.flow.is_empty() || (doc.keep_next && keep) { - mem::take(&mut self.flow).finish(doc, styles); - } - doc.keep_next = !keep; - } - } - - Ok(()) - } - - fn sequence( - &mut self, - seq: &'a SequenceNode, - styles: StyleChain<'a>, - ) -> SourceResult<()> { - for content in &seq.0 { - self.accept(content, styles)?; - } - Ok(()) - } -} - -/// Accepts pagebreaks and pages. -struct DocBuilder<'a> { - /// The page runs built so far. - pages: StyleVecBuilder<'a, PageNode>, - /// Whether to keep a following page even if it is empty. - keep_next: bool, -} - -impl<'a> DocBuilder<'a> { - fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) { - if let Some(pagebreak) = content.downcast::<PagebreakNode>() { - self.keep_next = !pagebreak.weak; - } - - if let Some(page) = content.downcast::<PageNode>() { - self.pages.push(page.clone(), styles); - self.keep_next = false; - } - } -} - -impl Default for DocBuilder<'_> { - fn default() -> Self { - Self { - pages: StyleVecBuilder::new(), - keep_next: true, - } - } -} - -/// Accepts flow content. -#[derive(Default)] -struct FlowBuilder<'a>(CollapsingBuilder<'a, FlowChild>); - -impl<'a> FlowBuilder<'a> { - fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { - // Weak flow elements: - // Weakness | Element - // 0 | weak colbreak - // 1 | weak fractional spacing - // 2 | weak spacing - // 3 | generated weak spacing - // 4 | generated weak fractional spacing - // 5 | par spacing - - if let Some(_) = content.downcast::<ParbreakNode>() { - /* Nothing to do */ - } else if let Some(colbreak) = content.downcast::<ColbreakNode>() { - if colbreak.weak { - self.0.weak(FlowChild::Colbreak, styles, 0); - } else { - self.0.destructive(FlowChild::Colbreak, styles); - } - } else if let Some(vertical) = content.downcast::<VNode>() { - let child = FlowChild::Spacing(vertical.amount); - let frac = vertical.amount.is_fractional(); - if vertical.weak { - let weakness = 1 + u8::from(frac) + 2 * u8::from(vertical.generated); - self.0.weak(child, styles, weakness); - } else if frac { - self.0.destructive(child, styles); - } else { - self.0.ignorant(child, styles); - } - } else if content.has::<dyn LayoutBlock>() { - let child = FlowChild::Block(content.clone()); - if content.is::<PlaceNode>() { - self.0.ignorant(child, styles); - } else { - self.0.supportive(child, styles); - } - } else { - return false; - } - - true - } - - fn par(&mut self, par: ParNode, styles: StyleChain<'a>, indent: bool) { - let amount = if indent && !styles.get(ParNode::SPACING_AND_INDENT) { - styles.get(ParNode::LEADING).into() - } else { - styles.get(ParNode::SPACING).into() - }; - - self.0.weak(FlowChild::Spacing(amount), styles, 5); - self.0.supportive(FlowChild::Block(par.pack()), styles); - self.0.weak(FlowChild::Spacing(amount), styles, 5); - } - - fn finish(self, doc: &mut DocBuilder<'a>, styles: StyleChain<'a>) { - let (flow, shared) = self.0.finish(); - let styles = if flow.is_empty() { styles } else { shared }; - let node = PageNode(FlowNode(flow).pack()); - doc.pages.push(node, styles); - } - - fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -/// Accepts paragraph content. -#[derive(Default)] -struct ParBuilder<'a>(CollapsingBuilder<'a, ParChild>); - -impl<'a> ParBuilder<'a> { - fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { - // Weak par elements: - // Weakness | Element - // 0 | weak fractional spacing - // 1 | weak spacing - // 2 | space - - if content.is::<SpaceNode>() { - self.0.weak(ParChild::Text(' '.into()), styles, 2); - } else if let Some(linebreak) = content.downcast::<LinebreakNode>() { - let c = if linebreak.justify { '\u{2028}' } else { '\n' }; - self.0.destructive(ParChild::Text(c.into()), styles); - } else if let Some(horizontal) = content.downcast::<HNode>() { - let child = ParChild::Spacing(horizontal.amount); - let frac = horizontal.amount.is_fractional(); - if horizontal.weak { - let weakness = u8::from(!frac); - self.0.weak(child, styles, weakness); - } else if frac { - self.0.destructive(child, styles); - } else { - self.0.ignorant(child, styles); - } - } else if let Some(quote) = content.downcast::<SmartQuoteNode>() { - self.0.supportive(ParChild::Quote { double: quote.double }, styles); - } else if let Some(text) = content.downcast::<TextNode>() { - self.0.supportive(ParChild::Text(text.0.clone()), styles); - } else if content.has::<dyn LayoutInline>() { - self.0.supportive(ParChild::Inline(content.clone()), styles); - } else { - return false; - } - - true - } - - fn finish(self, parent: &mut Builder<'a>) { - let (mut children, shared) = self.0.finish(); - if children.is_empty() { - return; - } - - // Paragraph indent should only apply if the paragraph starts with - // text and follows directly after another paragraph. - let indent = shared.get(ParNode::INDENT); - if !indent.is_zero() - && children - .items() - .find_map(|child| match child { - ParChild::Spacing(_) => None, - ParChild::Text(_) | ParChild::Quote { .. } => Some(true), - ParChild::Inline(_) => Some(false), - }) - .unwrap_or_default() - && parent - .flow - .0 - .items() - .rev() - .find_map(|child| match child { - FlowChild::Spacing(_) => None, - FlowChild::Block(content) => Some(content.is::<ParNode>()), - FlowChild::Colbreak => Some(false), - }) - .unwrap_or_default() - { - children.push_front(ParChild::Spacing(indent.into())); - } - - parent.flow.par(ParNode(children), shared, !indent.is_zero()); - } - - fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -/// Accepts list / enum items, spaces, paragraph breaks. -struct ListBuilder<'a> { - /// The list items collected so far. - items: StyleVecBuilder<'a, ListItem>, - /// Whether the list contains no paragraph breaks. - tight: bool, - /// Whether the list can be attached. - attachable: bool, - /// Trailing content for which it is unclear whether it is part of the list. - staged: Vec<(&'a Content, StyleChain<'a>)>, -} - -impl<'a> ListBuilder<'a> { - fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { - if self.items.is_empty() { - if content.is::<ParbreakNode>() { - self.attachable = false; - } else if !content.is::<SpaceNode>() && !content.is::<ListItem>() { - self.attachable = true; - } - } - - if let Some(item) = content.downcast::<ListItem>() { - if self - .items - .items() - .next() - .map_or(true, |first| item.kind() == first.kind()) - { - self.items.push(item.clone(), styles); - self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakNode>()); - } else { - return false; - } - } else if !self.items.is_empty() - && (content.is::<SpaceNode>() || content.is::<ParbreakNode>()) - { - self.staged.push((content, styles)); - } else { - return false; - } - - true - } - - fn finish(self, parent: &mut Builder<'a>) -> SourceResult<()> { - let (items, shared) = self.items.finish(); - let kind = match items.items().next() { - Some(item) => item.kind(), - None => return Ok(()), - }; - - let tight = self.tight; - let attached = tight && self.attachable; - let content = match kind { - LIST => ListNode::<LIST> { tight, attached, items }.pack(), - ENUM => ListNode::<ENUM> { tight, attached, items }.pack(), - DESC | _ => ListNode::<DESC> { tight, attached, items }.pack(), - }; - - let stored = parent.scratch.templates.alloc(content); - parent.accept(stored, shared)?; - - for (content, styles) in self.staged { - parent.accept(content, styles)?; - } - - parent.list.attachable = true; - - Ok(()) - } - - fn is_empty(&self) -> bool { - self.items.is_empty() - } -} - -impl Default for ListBuilder<'_> { - fn default() -> Self { - Self { - items: StyleVecBuilder::default(), - tight: true, - attachable: true, - staged: vec![], - } - } -} - -/// A wrapper around a [`StyleVecBuilder`] that allows to collapse items. -struct CollapsingBuilder<'a, T> { - /// The internal builder. - builder: StyleVecBuilder<'a, T>, - /// Staged weak and ignorant items that we can't yet commit to the builder. - /// The option is `Some(_)` for weak items and `None` for ignorant items. - staged: Vec<(T, StyleChain<'a>, Option<u8>)>, - /// What the last non-ignorant item was. - last: Last, -} - -/// What the last non-ignorant item was. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -enum Last { - Weak, - Destructive, - Supportive, -} - -impl<'a, T> CollapsingBuilder<'a, T> { - /// Create a new style-vec builder. - pub fn new() -> Self { - Self { - builder: StyleVecBuilder::new(), - staged: vec![], - last: Last::Destructive, - } - } - - /// Whether the builder is empty. - pub fn is_empty(&self) -> bool { - self.builder.is_empty() && self.staged.is_empty() - } - - /// Can only exist when there is at least one supportive item to its left - /// and to its right, with no destructive items in between. There may be - /// ignorant items in between in both directions. - /// - /// Between weak items, there may be at least one per layer and among the - /// candidates the strongest one (smallest `weakness`) wins. When tied, - /// the one that compares larger through `PartialOrd` wins. - pub fn weak(&mut self, item: T, styles: StyleChain<'a>, weakness: u8) - where - T: PartialOrd, - { - if self.last == Last::Destructive { - return; - } - - if self.last == Last::Weak { - if let Some(i) = - self.staged.iter().position(|(prev_item, _, prev_weakness)| { - prev_weakness.map_or(false, |prev_weakness| { - weakness < prev_weakness - || (weakness == prev_weakness && item > *prev_item) - }) - }) - { - self.staged.remove(i); - } else { - return; - } - } - - self.staged.push((item, styles, Some(weakness))); - self.last = Last::Weak; - } - - /// Forces nearby weak items to collapse. - pub fn destructive(&mut self, item: T, styles: StyleChain<'a>) { - self.flush(false); - self.builder.push(item, styles); - self.last = Last::Destructive; - } - - /// Allows nearby weak items to exist. - pub fn supportive(&mut self, item: T, styles: StyleChain<'a>) { - self.flush(true); - self.builder.push(item, styles); - self.last = Last::Supportive; - } - - /// Has no influence on other items. - pub fn ignorant(&mut self, item: T, styles: StyleChain<'a>) { - self.staged.push((item, styles, None)); - } - - /// Iterate over the contained items. - pub fn items(&self) -> impl DoubleEndedIterator<Item = &T> { - self.builder.items().chain(self.staged.iter().map(|(item, ..)| item)) - } - - /// Return the finish style vec and the common prefix chain. - pub fn finish(mut self) -> (StyleVec<T>, StyleChain<'a>) { - self.flush(false); - self.builder.finish() - } - - /// Push the staged items, filtering out weak items if `supportive` is - /// false. - fn flush(&mut self, supportive: bool) { - for (item, styles, meta) in self.staged.drain(..) { - if supportive || meta.is_none() { - self.builder.push(item, styles); - } - } - } -} - -impl<'a, T> Default for CollapsingBuilder<'a, T> { - fn default() -> Self { - Self::new() - } -} diff --git a/src/library/layout/pad.rs b/src/library/layout/pad.rs deleted file mode 100644 index 920660d6..00000000 --- a/src/library/layout/pad.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::library::prelude::*; - -/// Pad content at the sides. -#[derive(Debug, Hash)] -pub struct PadNode { - /// The amount of padding. - pub padding: Sides<Rel<Length>>, - /// The content whose sides to pad. - pub child: Content, -} - -#[node(LayoutBlock)] -impl PadNode { - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - let all = args.named("rest")?.or(args.find()?); - let x = args.named("x")?; - let y = args.named("y")?; - let left = args.named("left")?.or(x).or(all).unwrap_or_default(); - let top = args.named("top")?.or(y).or(all).unwrap_or_default(); - let right = args.named("right")?.or(x).or(all).unwrap_or_default(); - let bottom = args.named("bottom")?.or(y).or(all).unwrap_or_default(); - let body = args.expect::<Content>("body")?; - let padding = Sides::new(left, top, right, bottom); - Ok(body.padded(padding)) - } -} - -impl LayoutBlock for PadNode { - fn layout_block( - &self, - world: Tracked<dyn World>, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult<Vec<Frame>> { - // Layout child into padded regions. - let padding = self.padding.resolve(styles); - let pod = regions.map(|size| shrink(size, padding)); - let mut frames = self.child.layout_block(world, &pod, styles)?; - - for frame in &mut frames { - // Apply the padding inversely such that the grown size padded - // yields the frame's size. - let padded = grow(frame.size(), padding); - let padding = padding.relative_to(padded); - let offset = Point::new(padding.left, padding.top); - - // Grow the frame and translate everything in the frame inwards. - frame.set_size(padded); - frame.translate(offset); - } - - Ok(frames) - } -} - -/// Shrink a size by padding relative to the size itself. -fn shrink(size: Size, padding: Sides<Rel<Abs>>) -> Size { - size - padding.relative_to(size).sum_by_axis() -} - -/// Grow a size by padding relative to the grown size. -/// This is the inverse operation to `shrink()`. -/// -/// For the horizontal axis the derivation looks as follows. -/// (Vertical axis is analogous.) -/// -/// Let w be the grown target width, -/// s be the given width, -/// l be the left padding, -/// r be the right padding, -/// p = l + r. -/// -/// We want that: w - l.resolve(w) - r.resolve(w) = s -/// -/// Thus: w - l.resolve(w) - r.resolve(w) = s -/// <=> w - p.resolve(w) = s -/// <=> w - p.rel * w - p.abs = s -/// <=> (1 - p.rel) * w = s + p.abs -/// <=> w = (s + p.abs) / (1 - p.rel) -fn grow(size: Size, padding: Sides<Rel<Abs>>) -> Size { - size.zip(padding.sum_by_axis()) - .map(|(s, p)| (s + p.abs).safe_div(1.0 - p.rel.get())) -} diff --git a/src/library/layout/page.rs b/src/library/layout/page.rs deleted file mode 100644 index 8d081749..00000000 --- a/src/library/layout/page.rs +++ /dev/null @@ -1,421 +0,0 @@ -use std::str::FromStr; - -use super::ColumnsNode; -use crate::library::prelude::*; - -/// Layouts its child onto one or multiple pages. -#[derive(PartialEq, Clone, Hash)] -pub struct PageNode(pub Content); - -#[node] -impl PageNode { - /// The unflipped width of the page. - #[property(resolve)] - pub const WIDTH: Smart<Length> = Smart::Custom(Paper::A4.width().into()); - /// The unflipped height of the page. - #[property(resolve)] - pub const HEIGHT: Smart<Length> = Smart::Custom(Paper::A4.height().into()); - /// Whether the page is flipped into landscape orientation. - pub const FLIPPED: bool = false; - - /// The page's margins. - #[property(fold)] - pub const MARGINS: Sides<Option<Smart<Rel<Length>>>> = Sides::splat(Smart::Auto); - - /// How many columns the page has. - pub const COLUMNS: NonZeroUsize = NonZeroUsize::new(1).unwrap(); - /// The page's background color. - pub const FILL: Option<Paint> = None; - - /// The page's header. - #[property(referenced)] - pub const HEADER: Marginal = Marginal::None; - /// The page's footer. - #[property(referenced)] - pub const FOOTER: Marginal = Marginal::None; - /// Content in the page's background. - #[property(referenced)] - pub const BACKGROUND: Marginal = Marginal::None; - /// Content in the page's foreground. - #[property(referenced)] - pub const FOREGROUND: Marginal = Marginal::None; - - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Self(args.expect("body")?).pack()) - } - - fn set(...) { - if let Some(paper) = args.named_or_find::<Paper>("paper")? { - styles.set(Self::WIDTH, Smart::Custom(paper.width().into())); - styles.set(Self::HEIGHT, Smart::Custom(paper.height().into())); - } - } -} - -impl PageNode { - /// Layout the page run into a sequence of frames, one per page. - pub fn layout( - &self, - world: Tracked<dyn World>, - mut page: usize, - styles: StyleChain, - ) -> SourceResult<Vec<Frame>> { - // When one of the lengths is infinite the page fits its content along - // that axis. - let width = styles.get(Self::WIDTH).unwrap_or(Abs::inf()); - let height = styles.get(Self::HEIGHT).unwrap_or(Abs::inf()); - let mut size = Size::new(width, height); - if styles.get(Self::FLIPPED) { - std::mem::swap(&mut size.x, &mut size.y); - } - - let mut min = width.min(height); - if !min.is_finite() { - min = Paper::A4.width(); - } - - // Determine the margins. - let default = Rel::from(0.1190 * min); - let padding = styles.get(Self::MARGINS).map(|side| side.unwrap_or(default)); - - let mut child = self.0.clone(); - - // Realize columns. - let columns = styles.get(Self::COLUMNS); - if columns.get() > 1 { - child = ColumnsNode { columns, child: self.0.clone() }.pack(); - } - - // Realize margins. - child = child.padded(padding); - - // Realize background fill. - if let Some(fill) = styles.get(Self::FILL) { - child = child.filled(fill); - } - - // Layout the child. - let regions = Regions::repeat(size, size, size.map(Abs::is_finite)); - let mut frames = child.layout_block(world, ®ions, styles)?; - - let header = styles.get(Self::HEADER); - let footer = styles.get(Self::FOOTER); - let foreground = styles.get(Self::FOREGROUND); - let background = styles.get(Self::BACKGROUND); - - // Realize overlays. - for frame in &mut frames { - let size = frame.size(); - let pad = padding.resolve(styles).relative_to(size); - let pw = size.x - pad.left - pad.right; - let py = size.y - pad.bottom; - for (role, marginal, pos, area) in [ - ( - Role::Header, - header, - Point::with_x(pad.left), - Size::new(pw, pad.top), - ), - ( - Role::Footer, - footer, - Point::new(pad.left, py), - Size::new(pw, pad.bottom), - ), - (Role::Foreground, foreground, Point::zero(), size), - (Role::Background, background, Point::zero(), size), - ] { - if let Some(content) = marginal.resolve(world, page)? { - let pod = Regions::one(area, area, Axes::splat(true)); - let mut sub = content.layout_block(world, &pod, styles)?.remove(0); - sub.apply_role(role); - - if role == Role::Background { - frame.prepend_frame(pos, sub); - } else { - frame.push_frame(pos, sub); - } - } - } - - page += 1; - } - - Ok(frames) - } -} - -impl Debug for PageNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("Page(")?; - self.0.fmt(f)?; - f.write_str(")") - } -} - -/// A page break. -#[derive(Debug, Copy, Clone, Hash)] -pub struct PagebreakNode { - pub weak: bool, -} - -#[node] -impl PagebreakNode { - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - let weak = args.named("weak")?.unwrap_or(false); - Ok(Self { weak }.pack()) - } -} - -/// A header, footer, foreground or background definition. -#[derive(Debug, Clone, PartialEq, Hash)] -pub enum Marginal { - /// Nothing, - None, - /// Bare content. - Content(Content), - /// A closure mapping from a page number to content. - Func(Func, Span), -} - -impl Marginal { - /// Resolve the marginal based on the page number. - pub fn resolve( - &self, - world: Tracked<dyn World>, - page: usize, - ) -> SourceResult<Option<Content>> { - Ok(match self { - Self::None => None, - Self::Content(content) => Some(content.clone()), - Self::Func(func, span) => { - let args = Args::new(*span, [Value::Int(page as i64)]); - Some(func.call_detached(world, args)?.display(world)) - } - }) - } -} - -impl Cast<Spanned<Value>> for Marginal { - fn is(value: &Spanned<Value>) -> bool { - matches!(&value.v, Value::Content(_) | Value::Func(_)) - } - - fn cast(value: Spanned<Value>) -> StrResult<Self> { - match value.v { - Value::None => Ok(Self::None), - Value::Str(v) => Ok(Self::Content(TextNode(v.into()).pack())), - Value::Content(v) => Ok(Self::Content(v)), - Value::Func(v) => Ok(Self::Func(v, value.span)), - v => Err(format!( - "expected none, content or function, found {}", - v.type_name(), - )), - } - } -} - -/// Specification of a paper. -#[derive(Debug, Copy, Clone)] -pub struct Paper { - /// The width of the paper in millimeters. - width: f64, - /// The height of the paper in millimeters. - height: f64, -} - -impl Paper { - /// The width of the paper. - pub fn width(self) -> Abs { - Abs::mm(self.width) - } - - /// The height of the paper. - pub fn height(self) -> Abs { - Abs::mm(self.height) - } -} - -/// Defines paper constants and a paper parsing implementation. -macro_rules! papers { - ($(($var:ident: $width:expr, $height: expr, $($pats:tt)*))*) => { - /// Predefined papers. - /// - /// Each paper is parsable from its name in kebab-case. - impl Paper { - $(pub const $var: Self = Self { width: $width, height: $height };)* - } - - impl FromStr for Paper { - type Err = &'static str; - - fn from_str(name: &str) -> Result<Self, Self::Err> { - match name.to_lowercase().as_str() { - $($($pats)* => Ok(Self::$var),)* - _ => Err("invalid paper name"), - } - } - } - }; -} - -castable! { - Paper, - Expected: "string", - Value::Str(string) => Self::from_str(&string)?, -} - -// All paper sizes in mm. -// -// Resources: -// - https://papersizes.io/ -// - https://en.wikipedia.org/wiki/Paper_size -// - https://www.theedkins.co.uk/jo/units/oldunits/print.htm -// - https://vintagepaper.co/blogs/news/traditional-paper-sizes -papers! { - // ---------------------------------------------------------------------- // - // ISO 216 A Series - (A0: 841.0, 1189.0, "a0") - (A1: 594.0, 841.0, "a1") - (A2: 420.0, 594.0, "a2") - (A3: 297.0, 420.0, "a3") - (A4: 210.0, 297.0, "a4") - (A5: 148.0, 210.0, "a5") - (A6: 105.0, 148.0, "a6") - (A7: 74.0, 105.0, "a7") - (A8: 52.0, 74.0, "a8") - (A9: 37.0, 52.0, "a9") - (A10: 26.0, 37.0, "a10") - (A11: 18.0, 26.0, "a11") - - // ISO 216 B Series - (ISO_B1: 707.0, 1000.0, "iso-b1") - (ISO_B2: 500.0, 707.0, "iso-b2") - (ISO_B3: 353.0, 500.0, "iso-b3") - (ISO_B4: 250.0, 353.0, "iso-b4") - (ISO_B5: 176.0, 250.0, "iso-b5") - (ISO_B6: 125.0, 176.0, "iso-b6") - (ISO_B7: 88.0, 125.0, "iso-b7") - (ISO_B8: 62.0, 88.0, "iso-b8") - - // ISO 216 C Series - (ISO_C3: 324.0, 458.0, "iso-c3") - (ISO_C4: 229.0, 324.0, "iso-c4") - (ISO_C5: 162.0, 229.0, "iso-c5") - (ISO_C6: 114.0, 162.0, "iso-c6") - (ISO_C7: 81.0, 114.0, "iso-c7") - (ISO_C8: 57.0, 81.0, "iso-c8") - - // DIN D Series (extension to ISO) - (DIN_D3: 272.0, 385.0, "din-d3") - (DIN_D4: 192.0, 272.0, "din-d4") - (DIN_D5: 136.0, 192.0, "din-d5") - (DIN_D6: 96.0, 136.0, "din-d6") - (DIN_D7: 68.0, 96.0, "din-d7") - (DIN_D8: 48.0, 68.0, "din-d8") - - // SIS (used in academia) - (SIS_G5: 169.0, 239.0, "sis-g5") - (SIS_E5: 115.0, 220.0, "sis-e5") - - // ANSI Extensions - (ANSI_A: 216.0, 279.0, "ansi-a") - (ANSI_B: 279.0, 432.0, "ansi-b") - (ANSI_C: 432.0, 559.0, "ansi-c") - (ANSI_D: 559.0, 864.0, "ansi-d") - (ANSI_E: 864.0, 1118.0, "ansi-e") - - // ANSI Architectural Paper - (ARCH_A: 229.0, 305.0, "arch-a") - (ARCH_B: 305.0, 457.0, "arch-b") - (ARCH_C: 457.0, 610.0, "arch-c") - (ARCH_D: 610.0, 914.0, "arch-d") - (ARCH_E1: 762.0, 1067.0, "arch-e1") - (ARCH_E: 914.0, 1219.0, "arch-e") - - // JIS B Series - (JIS_B0: 1030.0, 1456.0, "jis-b0") - (JIS_B1: 728.0, 1030.0, "jis-b1") - (JIS_B2: 515.0, 728.0, "jis-b2") - (JIS_B3: 364.0, 515.0, "jis-b3") - (JIS_B4: 257.0, 364.0, "jis-b4") - (JIS_B5: 182.0, 257.0, "jis-b5") - (JIS_B6: 128.0, 182.0, "jis-b6") - (JIS_B7: 91.0, 128.0, "jis-b7") - (JIS_B8: 64.0, 91.0, "jis-b8") - (JIS_B9: 45.0, 64.0, "jis-b9") - (JIS_B10: 32.0, 45.0, "jis-b10") - (JIS_B11: 22.0, 32.0, "jis-b11") - - // SAC D Series - (SAC_D0: 764.0, 1064.0, "sac-d0") - (SAC_D1: 532.0, 760.0, "sac-d1") - (SAC_D2: 380.0, 528.0, "sac-d2") - (SAC_D3: 264.0, 376.0, "sac-d3") - (SAC_D4: 188.0, 260.0, "sac-d4") - (SAC_D5: 130.0, 184.0, "sac-d5") - (SAC_D6: 92.0, 126.0, "sac-d6") - - // ISO 7810 ID - (ISO_ID_1: 85.6, 53.98, "iso-id-1") - (ISO_ID_2: 74.0, 105.0, "iso-id-2") - (ISO_ID_3: 88.0, 125.0, "iso-id-3") - - // ---------------------------------------------------------------------- // - // Asia - (ASIA_F4: 210.0, 330.0, "asia-f4") - - // Japan - (JP_SHIROKU_BAN_4: 264.0, 379.0, "jp-shiroku-ban-4") - (JP_SHIROKU_BAN_5: 189.0, 262.0, "jp-shiroku-ban-5") - (JP_SHIROKU_BAN_6: 127.0, 188.0, "jp-shiroku-ban-6") - (JP_KIKU_4: 227.0, 306.0, "jp-kiku-4") - (JP_KIKU_5: 151.0, 227.0, "jp-kiku-5") - (JP_BUSINESS_CARD: 91.0, 55.0, "jp-business-card") - - // China - (CN_BUSINESS_CARD: 90.0, 54.0, "cn-business-card") - - // Europe - (EU_BUSINESS_CARD: 85.0, 55.0, "eu-business-card") - - // French Traditional (AFNOR) - (FR_TELLIERE: 340.0, 440.0, "fr-tellière") - (FR_COURONNE_ECRITURE: 360.0, 460.0, "fr-couronne-écriture") - (FR_COURONNE_EDITION: 370.0, 470.0, "fr-couronne-édition") - (FR_RAISIN: 500.0, 650.0, "fr-raisin") - (FR_CARRE: 450.0, 560.0, "fr-carré") - (FR_JESUS: 560.0, 760.0, "fr-jésus") - - // United Kingdom Imperial - (UK_BRIEF: 406.4, 342.9, "uk-brief") - (UK_DRAFT: 254.0, 406.4, "uk-draft") - (UK_FOOLSCAP: 203.2, 330.2, "uk-foolscap") - (UK_QUARTO: 203.2, 254.0, "uk-quarto") - (UK_CROWN: 508.0, 381.0, "uk-crown") - (UK_BOOK_A: 111.0, 178.0, "uk-book-a") - (UK_BOOK_B: 129.0, 198.0, "uk-book-b") - - // Unites States - (US_LETTER: 215.9, 279.4, "us-letter") - (US_LEGAL: 215.9, 355.6, "us-legal") - (US_TABLOID: 279.4, 431.8, "us-tabloid") - (US_EXECUTIVE: 84.15, 266.7, "us-executive") - (US_FOOLSCAP_FOLIO: 215.9, 342.9, "us-foolscap-folio") - (US_STATEMENT: 139.7, 215.9, "us-statement") - (US_LEDGER: 431.8, 279.4, "us-ledger") - (US_OFICIO: 215.9, 340.36, "us-oficio") - (US_GOV_LETTER: 203.2, 266.7, "us-gov-letter") - (US_GOV_LEGAL: 215.9, 330.2, "us-gov-legal") - (US_BUSINESS_CARD: 88.9, 50.8, "us-business-card") - (US_DIGEST: 139.7, 215.9, "us-digest") - (US_TRADE: 152.4, 228.6, "us-trade") - - // ---------------------------------------------------------------------- // - // Other - (NEWSPAPER_COMPACT: 280.0, 430.0, "newspaper-compact") - (NEWSPAPER_BERLINER: 315.0, 470.0, "newspaper-berliner") - (NEWSPAPER_BROADSHEET: 381.0, 578.0, "newspaper-broadsheet") - (PRESENTATION_16_9: 297.0, 167.0625, "presentation-16-9") - (PRESENTATION_4_3: 280.0, 210.0, "presentation-4-3") -} diff --git a/src/library/layout/place.rs b/src/library/layout/place.rs deleted file mode 100644 index ee38ebe6..00000000 --- a/src/library/layout/place.rs +++ /dev/null @@ -1,56 +0,0 @@ -use super::AlignNode; -use crate::library::prelude::*; - -/// Place content at an absolute position. -#[derive(Debug, Hash)] -pub struct PlaceNode(pub Content); - -#[node(LayoutBlock)] -impl PlaceNode { - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - let aligns = args.find()?.unwrap_or(Axes::with_x(Some(RawAlign::Start))); - let dx = args.named("dx")?.unwrap_or_default(); - let dy = args.named("dy")?.unwrap_or_default(); - let body = args.expect::<Content>("body")?; - Ok(Self(body.moved(Axes::new(dx, dy)).aligned(aligns)).pack()) - } -} - -impl LayoutBlock for PlaceNode { - fn layout_block( - &self, - world: Tracked<dyn World>, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult<Vec<Frame>> { - let out_of_flow = self.out_of_flow(); - - // The pod is the base area of the region because for absolute - // placement we don't really care about the already used area. - let pod = { - let finite = regions.base.map(Abs::is_finite); - let expand = finite & (regions.expand | out_of_flow); - Regions::one(regions.base, regions.base, expand) - }; - - let mut frames = self.0.layout_block(world, &pod, styles)?; - - // If expansion is off, zero all sizes so that we don't take up any - // space in our parent. Otherwise, respect the expand settings. - let target = regions.expand.select(regions.first, Size::zero()); - frames[0].resize(target, Align::LEFT_TOP); - - Ok(frames) - } -} - -impl PlaceNode { - /// Whether this node wants to be placed relative to its its parent's base - /// origin. Instead of relative to the parent's current flow/cursor - /// position. - pub fn out_of_flow(&self) -> bool { - self.0 - .downcast::<AlignNode>() - .map_or(false, |node| node.aligns.y.is_some()) - } -} diff --git a/src/library/layout/spacing.rs b/src/library/layout/spacing.rs deleted file mode 100644 index c410eee7..00000000 --- a/src/library/layout/spacing.rs +++ /dev/null @@ -1,100 +0,0 @@ -use std::cmp::Ordering; - -use crate::library::prelude::*; -use crate::library::text::ParNode; - -/// Horizontal spacing. -#[derive(Debug, Clone, Hash)] -pub struct HNode { - pub amount: Spacing, - pub weak: bool, -} - -#[node] -impl HNode { - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - let amount = args.expect("spacing")?; - let weak = args.named("weak")?.unwrap_or(false); - Ok(Self { amount, weak }.pack()) - } -} - -/// Vertical spacing. -#[derive(Debug, Clone, Hash)] -pub struct VNode { - pub amount: Spacing, - pub weak: bool, - pub generated: bool, -} - -#[node] -impl VNode { - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - let amount = args.expect("spacing")?; - let weak = args.named("weak")?.unwrap_or(false); - Ok(Self { amount, weak, generated: false }.pack()) - } -} - -/// Kinds of spacing. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum Spacing { - /// Spacing specified in absolute terms and relative to the parent's size. - Relative(Rel<Length>), - /// Spacing specified as a fraction of the remaining free space in the - /// parent. - Fractional(Fr), -} - -impl Spacing { - /// Whether this is fractional spacing. - pub fn is_fractional(self) -> bool { - matches!(self, Self::Fractional(_)) - } -} - -impl From<Abs> for Spacing { - fn from(abs: Abs) -> Self { - Self::Relative(abs.into()) - } -} - -impl PartialOrd for Spacing { - fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - match (self, other) { - (Self::Relative(a), Self::Relative(b)) => a.partial_cmp(b), - (Self::Fractional(a), Self::Fractional(b)) => a.partial_cmp(b), - _ => None, - } - } -} - -castable! { - Spacing, - Expected: "relative length or fraction", - Value::Length(v) => Self::Relative(v.into()), - Value::Ratio(v) => Self::Relative(v.into()), - Value::Relative(v) => Self::Relative(v), - Value::Fraction(v) => Self::Fractional(v), -} - -/// Spacing around and between blocks, relative to paragraph spacing. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct BlockSpacing(Rel<Length>); - -castable!(BlockSpacing: Rel<Length>); - -impl Resolve for BlockSpacing { - type Output = Abs; - - fn resolve(self, styles: StyleChain) -> Self::Output { - let whole = styles.get(ParNode::SPACING); - self.0.resolve(styles).relative_to(whole) - } -} - -impl From<Ratio> for BlockSpacing { - fn from(ratio: Ratio) -> Self { - Self(ratio.into()) - } -} diff --git a/src/library/layout/stack.rs b/src/library/layout/stack.rs deleted file mode 100644 index e1e70de9..00000000 --- a/src/library/layout/stack.rs +++ /dev/null @@ -1,321 +0,0 @@ -use super::{AlignNode, Spacing}; -use crate::library::prelude::*; -use crate::library::text::ParNode; -use crate::model::StyledNode; - -/// Arrange content and spacing along an axis. -#[derive(Debug, Hash)] -pub struct StackNode { - /// The stacking direction. - pub dir: Dir, - /// The spacing between non-spacing children. - pub spacing: Option<Spacing>, - /// The children to be stacked. - pub children: Vec<StackChild>, -} - -#[node(LayoutBlock)] -impl StackNode { - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Self { - dir: args.named("dir")?.unwrap_or(Dir::TTB), - spacing: args.named("spacing")?, - children: args.all()?, - } - .pack()) - } -} - -impl LayoutBlock for StackNode { - fn layout_block( - &self, - world: Tracked<dyn World>, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult<Vec<Frame>> { - let mut layouter = StackLayouter::new(self.dir, regions, styles); - - // Spacing to insert before the next block. - let mut deferred = None; - - for child in &self.children { - match child { - StackChild::Spacing(kind) => { - layouter.layout_spacing(*kind); - deferred = None; - } - StackChild::Block(block) => { - if let Some(kind) = deferred { - layouter.layout_spacing(kind); - } - - layouter.layout_block(world, block, styles)?; - deferred = self.spacing; - } - } - } - - Ok(layouter.finish()) - } -} - -/// A child of a stack node. -#[derive(Hash)] -pub enum StackChild { - /// Spacing between other children. - Spacing(Spacing), - /// Arbitrary block-level content. - Block(Content), -} - -impl Debug for StackChild { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Spacing(kind) => kind.fmt(f), - Self::Block(block) => block.fmt(f), - } - } -} - -castable! { - StackChild, - Expected: "relative length, fraction, or content", - Value::Length(v) => Self::Spacing(Spacing::Relative(v.into())), - Value::Ratio(v) => Self::Spacing(Spacing::Relative(v.into())), - Value::Relative(v) => Self::Spacing(Spacing::Relative(v)), - Value::Fraction(v) => Self::Spacing(Spacing::Fractional(v)), - Value::Content(v) => Self::Block(v), -} - -/// Performs stack layout. -pub struct StackLayouter<'a> { - /// The stacking direction. - dir: Dir, - /// The axis of the stacking direction. - axis: Axis, - /// The regions to layout children into. - regions: Regions, - /// The inherited styles. - styles: StyleChain<'a>, - /// Whether the stack itself should expand to fill the region. - expand: Axes<bool>, - /// The full size of the current region that was available at the start. - full: Size, - /// The generic size used by the frames for the current region. - used: Gen<Abs>, - /// The sum of fractions in the current region. - fr: Fr, - /// Already layouted items whose exact positions are not yet known due to - /// fractional spacing. - items: Vec<StackItem>, - /// Finished frames for previous regions. - finished: Vec<Frame>, -} - -/// A prepared item in a stack layout. -enum StackItem { - /// Absolute spacing between other items. - Absolute(Abs), - /// Fractional spacing between other items. - Fractional(Fr), - /// A frame for a layouted block. - Frame(Frame, Align), -} - -impl<'a> StackLayouter<'a> { - /// Create a new stack layouter. - pub fn new(dir: Dir, regions: &Regions, styles: StyleChain<'a>) -> Self { - let axis = dir.axis(); - let expand = regions.expand; - let full = regions.first; - - // Disable expansion along the block axis for children. - let mut regions = regions.clone(); - regions.expand.set(axis, false); - - Self { - dir, - axis, - regions, - styles, - expand, - full, - used: Gen::zero(), - fr: Fr::zero(), - items: vec![], - finished: vec![], - } - } - - /// Add spacing along the spacing direction. - pub fn layout_spacing(&mut self, spacing: Spacing) { - match spacing { - Spacing::Relative(v) => { - // Resolve the spacing and limit it to the remaining space. - let resolved = - v.resolve(self.styles).relative_to(self.regions.base.get(self.axis)); - let remaining = self.regions.first.get_mut(self.axis); - let limited = resolved.min(*remaining); - *remaining -= limited; - self.used.main += limited; - self.items.push(StackItem::Absolute(resolved)); - } - Spacing::Fractional(v) => { - self.fr += v; - self.items.push(StackItem::Fractional(v)); - } - } - } - - /// Layout an arbitrary block. - pub fn layout_block( - &mut self, - world: Tracked<dyn World>, - block: &Content, - styles: StyleChain, - ) -> SourceResult<()> { - if self.regions.is_full() { - self.finish_region(); - } - - // Block-axis alignment of the `AlignNode` is respected - // by the stack node. - let align = block - .downcast::<AlignNode>() - .and_then(|node| node.aligns.get(self.axis)) - .map(|align| align.resolve(styles)) - .unwrap_or_else(|| { - if let Some(styled) = block.downcast::<StyledNode>() { - let map = &styled.map; - if map.contains(ParNode::ALIGN) { - return StyleChain::with_root(map).get(ParNode::ALIGN); - } - } - - self.dir.start().into() - }); - - let frames = block.layout_block(world, &self.regions, styles)?; - let len = frames.len(); - for (i, mut frame) in frames.into_iter().enumerate() { - // Set the generic block role. - frame.apply_role(Role::GenericBlock); - - // Grow our size, shrink the region and save the frame for later. - let size = frame.size(); - let size = match self.axis { - Axis::X => Gen::new(size.y, size.x), - Axis::Y => Gen::new(size.x, size.y), - }; - - self.used.main += size.main; - self.used.cross.set_max(size.cross); - *self.regions.first.get_mut(self.axis) -= size.main; - self.items.push(StackItem::Frame(frame, align)); - - if i + 1 < len { - self.finish_region(); - } - } - - Ok(()) - } - - /// Advance to the next region. - pub fn finish_region(&mut self) { - // Determine the size of the stack in this region dependening on whether - // the region expands. - let used = self.used.to_axes(self.axis); - let mut size = self.expand.select(self.full, used); - - // Expand fully if there are fr spacings. - let full = self.full.get(self.axis); - let remaining = full - self.used.main; - if self.fr.get() > 0.0 && full.is_finite() { - self.used.main = full; - size.set(self.axis, full); - } - - let mut output = Frame::new(size); - let mut cursor = Abs::zero(); - let mut ruler: Align = self.dir.start().into(); - - // Place all frames. - for item in self.items.drain(..) { - match item { - StackItem::Absolute(v) => cursor += v, - StackItem::Fractional(v) => cursor += v.share(self.fr, remaining), - StackItem::Frame(frame, align) => { - if self.dir.is_positive() { - ruler = ruler.max(align); - } else { - ruler = ruler.min(align); - } - - // Align along the block axis. - let parent = size.get(self.axis); - let child = frame.size().get(self.axis); - let block = ruler.position(parent - self.used.main) - + if self.dir.is_positive() { - cursor - } else { - self.used.main - child - cursor - }; - - let pos = Gen::new(Abs::zero(), block).to_point(self.axis); - cursor += child; - output.push_frame(pos, frame); - } - } - } - - // Advance to the next region. - self.regions.next(); - self.full = self.regions.first; - self.used = Gen::zero(); - self.fr = Fr::zero(); - self.finished.push(output); - } - - /// Finish layouting and return the resulting frames. - pub fn finish(mut self) -> Vec<Frame> { - self.finish_region(); - self.finished - } -} - -/// A container with a main and cross component. -#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Gen<T> { - /// The main component. - pub cross: T, - /// The cross component. - pub main: T, -} - -impl<T> Gen<T> { - /// Create a new instance from the two components. - pub const fn new(cross: T, main: T) -> Self { - Self { cross, main } - } - - /// Convert to the specific representation, given the current main axis. - pub fn to_axes(self, main: Axis) -> Axes<T> { - match main { - Axis::X => Axes::new(self.main, self.cross), - Axis::Y => Axes::new(self.cross, self.main), - } - } -} - -impl Gen<Abs> { - /// The zero value. - pub fn zero() -> Self { - Self { cross: Abs::zero(), main: Abs::zero() } - } - - /// Convert to a point. - pub fn to_point(self, main: Axis) -> Point { - self.to_axes(main).to_point() - } -} diff --git a/src/library/layout/transform.rs b/src/library/layout/transform.rs deleted file mode 100644 index a73a1827..00000000 --- a/src/library/layout/transform.rs +++ /dev/null @@ -1,116 +0,0 @@ -use crate::geom::Transform; -use crate::library::prelude::*; - -/// Move content without affecting layout. -#[derive(Debug, Hash)] -pub struct MoveNode { - /// The offset by which to move the content. - pub delta: Axes<Rel<Length>>, - /// The content that should be moved. - pub child: Content, -} - -#[node(LayoutInline)] -impl MoveNode { - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - let dx = args.named("dx")?.unwrap_or_default(); - let dy = args.named("dy")?.unwrap_or_default(); - Ok(Self { - delta: Axes::new(dx, dy), - child: args.expect("body")?, - } - .pack()) - } -} - -impl LayoutInline for MoveNode { - fn layout_inline( - &self, - world: Tracked<dyn World>, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult<Vec<Frame>> { - let mut frames = self.child.layout_inline(world, regions, styles)?; - - let delta = self.delta.resolve(styles); - for frame in &mut frames { - let delta = delta.zip(frame.size()).map(|(d, s)| d.relative_to(s)); - frame.translate(delta.to_point()); - } - - Ok(frames) - } -} - -/// Transform content without affecting layout. -#[derive(Debug, Hash)] -pub struct TransformNode<const T: TransformKind> { - /// Transformation to apply to the content. - pub transform: Transform, - /// The content that should be transformed. - pub child: Content, -} - -/// Rotate content without affecting layout. -pub type RotateNode = TransformNode<ROTATE>; - -/// Scale content without affecting layout. -pub type ScaleNode = TransformNode<SCALE>; - -#[node(LayoutInline)] -impl<const T: TransformKind> TransformNode<T> { - /// The origin of the transformation. - #[property(resolve)] - pub const ORIGIN: Axes<Option<RawAlign>> = Axes::default(); - - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - let transform = match T { - ROTATE => { - let angle = args.named_or_find("angle")?.unwrap_or_default(); - Transform::rotate(angle) - } - SCALE | _ => { - let all = args.find()?; - let sx = args.named("x")?.or(all).unwrap_or(Ratio::one()); - let sy = args.named("y")?.or(all).unwrap_or(Ratio::one()); - Transform::scale(sx, sy) - } - }; - - Ok(Self { transform, child: args.expect("body")? }.pack()) - } -} - -impl<const T: TransformKind> LayoutInline for TransformNode<T> { - fn layout_inline( - &self, - world: Tracked<dyn World>, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult<Vec<Frame>> { - let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON); - let mut frames = self.child.layout_inline(world, regions, styles)?; - - for frame in &mut frames { - let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s)); - let transform = Transform::translate(x, y) - .pre_concat(self.transform) - .pre_concat(Transform::translate(-x, -y)); - - frame.transform(transform); - } - - Ok(frames) - } -} - -/// Kinds of transformations. -/// -/// The move transformation is handled separately. -pub type TransformKind = usize; - -/// A rotational transformation. -const ROTATE: TransformKind = 1; - -/// A scale transformation. -const SCALE: TransformKind = 2; |
