//! Layouting infrastructure. mod constraints; #[cfg(feature = "layout-cache")] mod incremental; mod regions; pub use constraints::*; #[cfg(feature = "layout-cache")] pub use incremental::*; pub use regions::*; use std::any::Any; use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; use crate::eval::{StyleChain, Styled}; use crate::font::FontStore; use crate::frame::{Element, Frame, Geometry, Shape, Stroke}; use crate::geom::{Align, Linear, Paint, Point, Sides, Size, Spec}; use crate::image::ImageStore; use crate::library::{AlignNode, Move, PadNode, PageNode, TransformNode}; use crate::Context; /// The root layout node, a document consisting of top-level page runs. #[derive(Hash)] pub struct RootNode(pub Vec>); impl RootNode { /// Layout the document into a sequence of frames, one per page. pub fn layout(&self, ctx: &mut Context) -> Vec> { let (mut ctx, styles) = LayoutContext::new(ctx); self.0 .iter() .flat_map(|styled| styled.item.layout(&mut ctx, styled.map.chain(&styles))) .collect() } } impl Debug for RootNode { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.write_str("Root ")?; f.debug_list().entries(&self.0).finish() } } /// 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, ctx: &mut LayoutContext, regions: &Regions, styles: StyleChain, ) -> Vec>>; /// Convert to a packed node. fn pack(self) -> PackedNode where Self: Debug + Hash + Sized + Sync + Send + 'static, { PackedNode { #[cfg(feature = "layout-cache")] hash: self.hash64(), node: Arc::new(self), } } } /// The context for layouting. pub struct LayoutContext<'a> { /// Stores parsed font faces. pub fonts: &'a mut FontStore, /// Stores decoded images. pub images: &'a mut ImageStore, /// Caches layouting artifacts. #[cfg(feature = "layout-cache")] pub layout_cache: &'a mut LayoutCache, /// How deeply nested the current layout tree position is. #[cfg(feature = "layout-cache")] level: usize, } impl<'a> LayoutContext<'a> { /// Create a new layout context. fn new(ctx: &'a mut Context) -> (Self, StyleChain<'a>) { let this = Self { fonts: &mut ctx.fonts, images: &mut ctx.images, #[cfg(feature = "layout-cache")] layout_cache: &mut ctx.layout_cache, #[cfg(feature = "layout-cache")] level: 0, }; (this, StyleChain::new(&ctx.styles)) } } /// A layout node that produces an empty frame. /// /// The packed version of this is returned by [`PackedNode::default`]. #[derive(Debug, Hash)] pub struct EmptyNode; impl Layout for EmptyNode { fn layout( &self, _: &mut LayoutContext, regions: &Regions, _: StyleChain, ) -> Vec>> { let size = regions.expand.select(regions.current, Size::zero()); let mut cts = Constraints::new(regions.expand); cts.exact = regions.current.filter(regions.expand); vec![Frame::new(size).constrain(cts)] } } /// A packed layouting node with a precomputed hash. #[derive(Clone)] pub struct PackedNode { /// The type-erased node. node: Arc, /// A precomputed hash for the node. #[cfg(feature = "layout-cache")] hash: u64, } impl PackedNode { /// Check whether the contained node is a specific layout node. pub fn is(&self) -> bool { self.node.as_any().is::() } /// Try to downcast to a specific layout node. pub fn downcast(&self) -> Option<&T> where T: Layout + Debug + Hash + 'static, { self.node.as_any().downcast_ref() } /// Force a size for this node. pub fn sized(self, sizing: Spec>) -> 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>) -> 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) -> 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 { kind: Move(offset.x, offset.y), child: self, } .pack() } else { self } } } impl Layout for PackedNode { #[track_caller] fn layout( &self, ctx: &mut LayoutContext, regions: &Regions, styles: StyleChain, ) -> Vec>> { let styles = styles.barred(self.node.as_any().type_id()); #[cfg(not(feature = "layout-cache"))] return self.node.layout(ctx, regions, styles); #[cfg(feature = "layout-cache")] let hash = { let mut state = fxhash::FxHasher64::default(); self.hash(&mut state); styles.hash(&mut state); state.finish() }; // This is not written with `unwrap_or_else`, because then the // #[track_caller] annotation doesn't work. #[cfg(feature = "layout-cache")] if let Some(frames) = ctx.layout_cache.get(hash, regions) { frames } else { ctx.level += 1; let frames = self.node.layout(ctx, regions, styles); ctx.level -= 1; let entry = FramesEntry::new(frames.clone(), ctx.level); #[cfg(debug_assertions)] if !entry.check(regions) { eprintln!("node: {:#?}", self.node); eprintln!("regions: {regions:#?}"); eprintln!( "constraints: {:#?}", frames.iter().map(|c| c.cts).collect::>(), ); panic!("constraints did not match regions they were created for"); } ctx.layout_cache.insert(hash, entry); frames } } fn pack(self) -> PackedNode { self } } impl Default for PackedNode { fn default() -> Self { EmptyNode.pack() } } impl Debug for PackedNode { fn fmt(&self, f: &mut Formatter) -> fmt::Result { self.node.fmt(f) } } impl PartialEq for PackedNode { fn eq(&self, other: &Self) -> bool { std::ptr::eq( Arc::as_ptr(&self.node) as *const (), Arc::as_ptr(&other.node) as *const (), ) } } impl Hash for PackedNode { fn hash(&self, state: &mut H) { // Hash the node. #[cfg(feature = "layout-cache")] state.write_u64(self.hash); #[cfg(not(feature = "layout-cache"))] state.write_u64(self.hash64()); } } trait Bounds: Layout + Debug + Sync + Send + 'static { fn as_any(&self) -> &dyn Any; fn hash64(&self) -> u64; } impl Bounds for T where T: Layout + Hash + Debug + Sync + Send + 'static, { fn as_any(&self) -> &dyn Any { self } fn hash64(&self) -> u64 { // Also hash the TypeId since nodes with different types but // equal data should be different. let mut state = fxhash::FxHasher64::default(); self.type_id().hash(&mut state); self.hash(&mut state); state.finish() } } /// A node that sizes its child. #[derive(Debug, Hash)] pub struct SizedNode { /// How to size the node horizontally and vertically. pub sizing: Spec>, /// The node to be sized. pub child: PackedNode, } impl Layout for SizedNode { fn layout( &self, ctx: &mut LayoutContext, regions: &Regions, styles: StyleChain, ) -> Vec>> { let is_auto = self.sizing.map_is_none(); let is_rel = self.sizing.map(|s| s.map_or(false, Linear::is_relative)); // 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.current); // Select the appropriate base and expansion for the child depending // on whether it is automatically or linearly sized. let base = is_auto.select(regions.base, size); let expand = regions.expand | !is_auto; Regions::one(size, base, expand) }; let mut frames = self.child.layout(ctx, &pod, styles); let Constrained { item: frame, cts } = &mut frames[0]; // Ensure frame size matches regions size if expansion is on. let target = regions.expand.select(regions.current, frame.size); Arc::make_mut(frame).resize(target, Align::LEFT_TOP); // Set base & exact constraints if the child is automatically sized // since we don't know what the child might have done. Also set base if // our sizing is relative. *cts = Constraints::new(regions.expand); cts.exact = regions.current.filter(regions.expand | is_auto); cts.base = regions.base.filter(is_rel | is_auto); frames } } /// Fill the frames resulting from a node. #[derive(Debug, Hash)] pub struct FillNode { /// How to fill the frames resulting from the `child`. pub fill: Paint, /// The node to fill. pub child: PackedNode, } impl Layout for FillNode { fn layout( &self, ctx: &mut LayoutContext, regions: &Regions, styles: StyleChain, ) -> Vec>> { let mut frames = self.child.layout(ctx, regions, styles); for Constrained { item: frame, .. } in &mut frames { let shape = Shape::filled(Geometry::Rect(frame.size), self.fill); Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape)); } frames } } /// Stroke the frames resulting from a node. #[derive(Debug, Hash)] pub struct StrokeNode { /// How to stroke the frames resulting from the `child`. pub stroke: Stroke, /// The node to stroke. pub child: PackedNode, } impl Layout for StrokeNode { fn layout( &self, ctx: &mut LayoutContext, regions: &Regions, styles: StyleChain, ) -> Vec>> { let mut frames = self.child.layout(ctx, regions, styles); for Constrained { item: frame, .. } in &mut frames { let shape = Shape::stroked(Geometry::Rect(frame.size), self.stroke); Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape)); } frames } }