diff options
| author | Martin <mhaug@live.de> | 2021-12-22 20:37:34 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-12-22 20:37:34 +0100 |
| commit | f6c7a8292dc1ab0560408fca9d74505e9d7cf13a (patch) | |
| tree | badd3076f6146cec34c55764600df5124c408521 /src/layout/mod.rs | |
| parent | 738ff7e1f573bef678932b313be9969a17af8d22 (diff) | |
| parent | 438255519e88bb790480306b9a9b452aaf054519 (diff) | |
Merge pull request #51 from typst/set-rules
Set rules
Diffstat (limited to 'src/layout/mod.rs')
| -rw-r--r-- | src/layout/mod.rs | 186 |
1 files changed, 145 insertions, 41 deletions
diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 9c57152a..114e7491 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -15,17 +15,57 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::rc::Rc; +use crate::eval::Styles; use crate::font::FontStore; use crate::frame::Frame; -use crate::geom::{Align, Linear, Point, Sides, Spec, Transform}; +use crate::geom::{Align, Linear, Point, Sides, Size, Spec, Transform}; use crate::image::ImageStore; -use crate::library::{AlignNode, DocumentNode, PadNode, SizedNode, TransformNode}; +use crate::library::{AlignNode, PadNode, PageNode, SizedNode, TransformNode}; use crate::Context; -/// Layout a document node into a collection of frames. -pub fn layout(ctx: &mut Context, node: &DocumentNode) -> Vec<Rc<Frame>> { - let mut ctx = LayoutContext::new(ctx); - node.layout(&mut ctx) +/// The root layout node, a document consisting of top-level page runs. +#[derive(Hash)] +pub struct RootNode(pub Vec<PageNode>); + +impl RootNode { + /// Layout the document into a sequence of frames, one per page. + pub fn layout(&self, ctx: &mut Context) -> Vec<Rc<Frame>> { + let mut ctx = LayoutContext::new(ctx); + self.0.iter().flat_map(|node| node.layout(&mut ctx)).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, + ) -> Vec<Constrained<Rc<Frame>>>; + + /// Convert to a packed node. + fn pack(self) -> PackedNode + where + Self: Debug + Hash + Sized + 'static, + { + PackedNode { + #[cfg(feature = "layout-cache")] + hash: self.hash64(), + node: Rc::new(self), + styles: Styles::new(), + } + } } /// The context for layouting. @@ -37,6 +77,9 @@ pub struct LayoutContext<'a> { /// Caches layouting artifacts. #[cfg(feature = "layout-cache")] pub layouts: &'a mut LayoutCache, + /// The inherited style properties. + // TODO(style): This probably shouldn't be here. + pub styles: Styles, /// How deeply nested the current layout tree position is. #[cfg(feature = "layout-cache")] pub level: usize, @@ -50,51 +93,50 @@ impl<'a> LayoutContext<'a> { images: &mut ctx.images, #[cfg(feature = "layout-cache")] layouts: &mut ctx.layouts, + styles: ctx.styles.clone(), #[cfg(feature = "layout-cache")] level: 0, } } } -/// A node that can be layouted into a sequence of regions. +/// A layout node that produces an empty frame. /// -/// 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. +/// The packed version of this is returned by [`PackedNode::default`]. +#[derive(Debug, Hash)] +pub struct EmptyNode; + +impl Layout for EmptyNode { fn layout( &self, - ctx: &mut LayoutContext, + _: &mut LayoutContext, regions: &Regions, - ) -> Vec<Constrained<Rc<Frame>>>; - - /// Convert to a packed node. - fn pack(self) -> PackedNode - where - Self: Debug + Hash + Sized + 'static, - { - PackedNode { - #[cfg(feature = "layout-cache")] - hash: { - let mut state = fxhash::FxHasher64::default(); - self.type_id().hash(&mut state); - self.hash(&mut state); - state.finish() - }, - node: Rc::new(self), - } + ) -> Vec<Constrained<Rc<Frame>>> { + 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 precomputed hash. +/// A packed layouting node with style properties and a precomputed hash. #[derive(Clone)] pub struct PackedNode { + /// The type-erased node. node: Rc<dyn Bounds>, + /// A precomputed hash for the node. #[cfg(feature = "layout-cache")] hash: u64, + /// The node's styles. + pub styles: Styles, } impl PackedNode { + /// Check whether the contained node is a specific layout node. + pub fn is<T: 'static>(&self) -> bool { + self.node.as_any().is::<T>() + } + /// Try to downcast to a specific layout node. pub fn downcast<T>(&self) -> Option<&T> where @@ -103,6 +145,16 @@ impl PackedNode { self.node.as_any().downcast_ref() } + /// Style the node with styles from a style map. + pub fn styled(mut self, styles: Styles) -> Self { + if self.styles.is_empty() { + self.styles = styles; + } else { + self.styles.apply(&styles); + } + self + } + /// Force a size for this node. pub fn sized(self, sizing: Spec<Option<Linear>>) -> Self { if sizing.any(Option::is_some) { @@ -156,12 +208,20 @@ impl Layout for PackedNode { regions: &Regions, ) -> Vec<Constrained<Rc<Frame>>> { #[cfg(not(feature = "layout-cache"))] - return self.node.layout(ctx, regions); + return self.layout_impl(ctx, regions); + + #[cfg(feature = "layout-cache")] + let hash = { + let mut state = fxhash::FxHasher64::default(); + self.hash(&mut state); + ctx.styles.hash(&mut state); + state.finish() + }; #[cfg(feature = "layout-cache")] - ctx.layouts.get(self.hash, regions).unwrap_or_else(|| { + ctx.layouts.get(hash, regions).unwrap_or_else(|| { ctx.level += 1; - let frames = self.node.layout(ctx, regions); + let frames = self.layout_impl(ctx, regions); ctx.level -= 1; let entry = FramesEntry::new(frames.clone(), ctx.level); @@ -177,7 +237,7 @@ impl Layout for PackedNode { panic!("constraints did not match regions they were created for"); } - ctx.layouts.insert(self.hash, entry); + ctx.layouts.insert(hash, entry); frames }) } @@ -190,30 +250,74 @@ impl Layout for PackedNode { } } -impl Hash for PackedNode { - fn hash<H: Hasher>(&self, _state: &mut H) { - #[cfg(feature = "layout-cache")] - _state.write_u64(self.hash); - #[cfg(not(feature = "layout-cache"))] - unimplemented!() +impl PackedNode { + /// Layout the node without checking the cache. + fn layout_impl( + &self, + ctx: &mut LayoutContext, + regions: &Regions, + ) -> Vec<Constrained<Rc<Frame>>> { + let new = self.styles.chain(&ctx.styles); + let prev = std::mem::replace(&mut ctx.styles, new); + let frames = self.node.layout(ctx, regions); + ctx.styles = prev; + frames + } +} + +impl Default for PackedNode { + fn default() -> Self { + EmptyNode.pack() } } impl Debug for PackedNode { fn fmt(&self, f: &mut Formatter) -> fmt::Result { + if f.alternate() { + self.styles.fmt(f)?; + } self.node.fmt(f) } } +impl PartialEq for PackedNode { + fn eq(&self, other: &Self) -> bool { + Rc::as_ptr(&self.node) as *const () == Rc::as_ptr(&other.node) as *const () + } +} + +impl Hash for PackedNode { + fn hash<H: Hasher>(&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()); + + // Hash the styles. + self.styles.hash(state); + } +} + trait Bounds: Layout + Debug + 'static { fn as_any(&self) -> &dyn Any; + fn hash64(&self) -> u64; } impl<T> Bounds for T where - T: Layout + Debug + 'static, + T: Layout + Hash + Debug + '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() + } } |
