diff options
Diffstat (limited to 'src/eval')
| -rw-r--r-- | src/eval/layout.rs | 381 | ||||
| -rw-r--r-- | src/eval/mod.rs | 2 | ||||
| -rw-r--r-- | src/eval/template.rs | 6 | ||||
| -rw-r--r-- | src/eval/value.rs | 3 |
4 files changed, 387 insertions, 5 deletions
diff --git a/src/eval/layout.rs b/src/eval/layout.rs new file mode 100644 index 00000000..7608023a --- /dev/null +++ b/src/eval/layout.rs @@ -0,0 +1,381 @@ +//! Layouting infrastructure. + +use std::any::{Any, TypeId}; +use std::fmt::{self, Debug, Formatter}; +use std::hash::Hash; +use std::sync::Arc; + +use crate::diag::TypResult; +use crate::eval::StyleChain; +use crate::frame::{Element, Frame, Geometry, Shape, Stroke}; +use crate::geom::{Align, Length, Linear, Paint, Point, Sides, Size, Spec, Transform}; +use crate::library::{AlignNode, PadNode, TransformNode, MOVE}; +use crate::util::Prehashed; +use crate::Vm; + +/// A node that can be layouted into a sequence of regions. +/// +/// Layout return one frame per used region alongside constraints that define +/// whether the result is reusable in other regions. +pub trait Layout { + /// Layout the node into the given regions, producing constrained frames. + fn layout( + &self, + vm: &mut Vm, + regions: &Regions, + styles: StyleChain, + ) -> TypResult<Vec<Arc<Frame>>>; + + /// Convert to a packed node. + fn pack(self) -> LayoutNode + where + Self: Debug + Hash + Sized + Sync + Send + 'static, + { + LayoutNode::new(self) + } +} + +/// A sequence of regions to layout into. +#[derive(Debug, Clone)] +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: std::vec::IntoIter<Length>, + /// 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<Length>, + /// Whether nodes should expand to fill the regions instead of shrinking to + /// fit the content. + pub expand: Spec<bool>, +} + +impl Regions { + /// Create a new region sequence with exactly one region. + pub fn one(size: Size, base: Size, expand: Spec<bool>) -> Self { + Self { + first: size, + base, + backlog: vec![].into_iter(), + last: None, + expand, + } + } + + /// Create a new sequence of same-size regions that repeats indefinitely. + pub fn repeat(size: Size, base: Size, expand: Spec<bool>) -> Self { + Self { + first: size, + base, + backlog: vec![].into_iter(), + 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 + .as_slice() + .iter() + .map(|&y| f(Size::new(x, y)).y) + .collect::<Vec<_>>() + .into_iter(), + 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 { + Length::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.next().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.as_slice().iter(); + let last = self.last.iter().cycle(); + first.chain(backlog.chain(last).map(|&h| Size::new(self.first.x, h))) + } +} + +/// A type-erased layouting node with a precomputed hash. +#[derive(Clone, Hash)] +pub struct LayoutNode(Arc<Prehashed<dyn Bounds>>); + +impl LayoutNode { + /// Pack any layoutable node. + pub fn new<T>(node: T) -> Self + where + T: Layout + Debug + Hash + Sync + Send + 'static, + { + Self(Arc::new(Prehashed::new(node))) + } + + /// Check whether the contained node is a specific layout node. + pub fn is<T: 'static>(&self) -> bool { + self.0.as_any().is::<T>() + } + + /// The type id of this node. + pub fn id(&self) -> TypeId { + self.0.as_any().type_id() + } + + /// Try to downcast to a specific layout node. + pub fn downcast<T>(&self) -> Option<&T> + where + T: Layout + Debug + Hash + 'static, + { + self.0.as_any().downcast_ref() + } + + /// Force a size for this node. + pub fn sized(self, sizing: Spec<Option<Linear>>) -> Self { + if sizing.any(Option::is_some) { + SizedNode { sizing, child: self }.pack() + } else { + self + } + } + + /// Fill the frames resulting from a node. + pub fn filled(self, fill: Paint) -> Self { + FillNode { fill, child: self }.pack() + } + + /// Stroke the frames resulting from a node. + pub fn stroked(self, stroke: Stroke) -> Self { + StrokeNode { stroke, child: self }.pack() + } + + /// Set alignments for this node. + pub fn aligned(self, aligns: Spec<Option<Align>>) -> Self { + if aligns.any(Option::is_some) { + AlignNode { aligns, child: self }.pack() + } else { + self + } + } + + /// Pad this node at the sides. + pub fn padded(self, padding: Sides<Linear>) -> Self { + if !padding.left.is_zero() + || !padding.top.is_zero() + || !padding.right.is_zero() + || !padding.bottom.is_zero() + { + PadNode { padding, child: self }.pack() + } else { + self + } + } + + /// Transform this node's contents without affecting layout. + pub fn moved(self, offset: Point) -> Self { + if !offset.is_zero() { + TransformNode::<MOVE> { + transform: Transform::translation(offset.x, offset.y), + child: self, + } + .pack() + } else { + self + } + } +} + +impl Layout for LayoutNode { + #[track_caller] + fn layout( + &self, + vm: &mut Vm, + regions: &Regions, + styles: StyleChain, + ) -> TypResult<Vec<Arc<Frame>>> { + // TODO(query) + self.0.layout(vm, regions, styles.barred(self.id())) + } + + fn pack(self) -> LayoutNode { + self + } +} + +impl Default for LayoutNode { + fn default() -> Self { + EmptyNode.pack() + } +} + +impl Debug for LayoutNode { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl PartialEq for LayoutNode { + fn eq(&self, other: &Self) -> bool { + self.0.eq(&other.0) + } +} + +trait Bounds: Layout + Debug + Sync + Send + 'static { + fn as_any(&self) -> &dyn Any; +} + +impl<T> Bounds for T +where + T: Layout + Debug + Hash + Sync + Send + 'static, +{ + fn as_any(&self) -> &dyn Any { + self + } +} + +/// A layout node that produces an empty frame. +/// +/// The packed version of this is returned by [`PackedNode::default`]. +#[derive(Debug, Hash)] +struct EmptyNode; + +impl Layout for EmptyNode { + fn layout( + &self, + _: &mut Vm, + regions: &Regions, + _: StyleChain, + ) -> TypResult<Vec<Arc<Frame>>> { + Ok(vec![Arc::new(Frame::new( + regions.expand.select(regions.first, Size::zero()), + ))]) + } +} + +/// Fix the size of a node. +#[derive(Debug, Hash)] +struct SizedNode { + /// How to size the node horizontally and vertically. + sizing: Spec<Option<Linear>>, + /// The node to be sized. + child: LayoutNode, +} + +impl Layout for SizedNode { + fn layout( + &self, + vm: &mut Vm, + regions: &Regions, + styles: StyleChain, + ) -> TypResult<Vec<Arc<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 + .zip(regions.base) + .map(|(s, b)| s.map(|v| v.resolve(b))) + .unwrap_or(regions.first); + + // Select the appropriate base and expansion for the child depending + // on whether it is automatically or linearly sized. + let is_auto = self.sizing.map_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(vm, &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); + Arc::make_mut(frame).resize(target, Align::LEFT_TOP); + + Ok(frames) + } +} + +/// Fill the frames resulting from a node. +#[derive(Debug, Hash)] +struct FillNode { + /// How to fill the frames resulting from the `child`. + fill: Paint, + /// The node to fill. + child: LayoutNode, +} + +impl Layout for FillNode { + fn layout( + &self, + vm: &mut Vm, + regions: &Regions, + styles: StyleChain, + ) -> TypResult<Vec<Arc<Frame>>> { + let mut frames = self.child.layout(vm, regions, styles)?; + for frame in &mut frames { + let shape = Shape::filled(Geometry::Rect(frame.size), self.fill); + Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape)); + } + Ok(frames) + } +} + +/// Stroke the frames resulting from a node. +#[derive(Debug, Hash)] +struct StrokeNode { + /// How to stroke the frames resulting from the `child`. + stroke: Stroke, + /// The node to stroke. + child: LayoutNode, +} + +impl Layout for StrokeNode { + fn layout( + &self, + vm: &mut Vm, + regions: &Regions, + styles: StyleChain, + ) -> TypResult<Vec<Arc<Frame>>> { + let mut frames = self.child.layout(vm, regions, styles)?; + for frame in &mut frames { + let shape = Shape::stroked(Geometry::Rect(frame.size), self.stroke); + Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape)); + } + Ok(frames) + } +} diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 1b61ac15..a67a21d4 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -12,6 +12,7 @@ mod capture; mod class; mod collapse; mod func; +mod layout; mod ops; mod scope; mod show; @@ -23,6 +24,7 @@ pub use class::*; pub use collapse::*; pub use dict::*; pub use func::*; +pub use layout::*; pub use scope::*; pub use show::*; pub use styles::*; diff --git a/src/eval/template.rs b/src/eval/template.rs index a1d2297d..b4e3c76d 100644 --- a/src/eval/template.rs +++ b/src/eval/template.rs @@ -7,10 +7,10 @@ use std::ops::{Add, AddAssign}; use typed_arena::Arena; use super::{ - CollapsingBuilder, Interruption, Property, Show, ShowNode, StyleMap, StyleVecBuilder, + CollapsingBuilder, Interruption, Layout, LayoutNode, Property, Show, ShowNode, + StyleMap, StyleVecBuilder, }; use crate::diag::StrResult; -use crate::layout::{Layout, LayoutNode}; use crate::library::prelude::*; use crate::library::{ DecoNode, FlowChild, FlowNode, ListItem, ListKind, ListNode, PageNode, ParChild, @@ -240,7 +240,7 @@ impl Layout for Template { vm: &mut Vm, regions: &Regions, styles: StyleChain, - ) -> TypResult<Vec<Constrained<Arc<Frame>>>> { + ) -> TypResult<Vec<Arc<Frame>>> { let sya = Arena::new(); let tpa = Arena::new(); diff --git a/src/eval/value.rs b/src/eval/value.rs index 952c7293..009a1463 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -4,10 +4,9 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; -use super::{ops, Args, Array, Class, Dict, Func, Template}; +use super::{ops, Args, Array, Class, Dict, Func, Layout, Template}; use crate::diag::StrResult; use crate::geom::{Angle, Color, Fractional, Length, Linear, Relative, RgbaColor}; -use crate::layout::Layout; use crate::syntax::Spanned; use crate::util::EcoString; |
