diff --git a/src/eval/template.rs b/src/eval/template.rs index 0604cc05e..2df347aad 100644 --- a/src/eval/template.rs +++ b/src/eval/template.rs @@ -58,7 +58,7 @@ impl Template { pub fn from_inline(f: F) -> Self where F: Fn(&Style) -> T + 'static, - T: Layout + Hash + 'static, + T: Layout + Debug + Hash + 'static, { let node = TemplateNode::Inline(Rc::new(move |s| f(s).pack())); Self(Rc::new(vec![node])) @@ -68,7 +68,7 @@ impl Template { pub fn from_block(f: F) -> Self where F: Fn(&Style) -> T + 'static, - T: Layout + Hash + 'static, + T: Layout + Debug + Hash + 'static, { let node = TemplateNode::Block(Rc::new(move |s| f(s).pack())); Self(Rc::new(vec![node])) @@ -332,14 +332,13 @@ impl Builder { /// Push an inline node into the active paragraph. fn inline(&mut self, node: impl Into) { - let align = self.style.aligns.inline; - self.flow.par.push(ParChild::Node(node.into(), align)); + self.flow.par.push(ParChild::Node(node.into())); } /// Push a block node into the active flow, finishing the active paragraph. fn block(&mut self, node: impl Into) { self.parbreak(); - self.flow.push(FlowChild::Node(node.into(), self.style.aligns.block)); + self.flow.push(FlowChild::Node(node.into())); self.parbreak(); } @@ -372,11 +371,7 @@ impl Builder { /// Construct a text node with the given text and settings from the current /// style. fn make_text_node(&self, text: impl Into) -> ParChild { - ParChild::Text( - text.into(), - self.style.aligns.inline, - Rc::clone(&self.style.text), - ) + ParChild::Text(text.into(), Rc::clone(&self.style.text)) } } @@ -451,8 +446,8 @@ impl FlowBuilder { } struct ParBuilder { - align: Align, dir: Dir, + align: Align, leading: Length, children: Vec, last: Last, @@ -461,8 +456,8 @@ struct ParBuilder { impl ParBuilder { fn new(style: &Style) -> Self { Self { - align: style.aligns.block, dir: style.par.dir, + align: style.par.align, leading: style.leading(), children: vec![], last: Last::None, @@ -486,12 +481,10 @@ impl ParBuilder { } fn push_inner(&mut self, child: ParChild) { - if let ParChild::Text(curr_text, curr_align, curr_props) = &child { - if let Some(ParChild::Text(prev_text, prev_align, prev_props)) = - self.children.last_mut() - { - if prev_align == curr_align && Rc::ptr_eq(prev_props, curr_props) { - prev_text.push_str(curr_text); + if let ParChild::Text(text2, style2) = &child { + if let Some(ParChild::Text(text1, style1)) = self.children.last_mut() { + if Rc::ptr_eq(style1, style2) { + text1.push_str(text2); return; } } @@ -501,9 +494,9 @@ impl ParBuilder { } fn build(self) -> Option { - let Self { align, dir, leading, children, .. } = self; + let Self { dir, align, leading, children, .. } = self; (!children.is_empty()) - .then(|| FlowChild::Node(ParNode { dir, leading, children }.pack(), align)) + .then(|| FlowChild::Node(ParNode { dir, align, leading, children }.pack())) } } diff --git a/src/eval/walk.rs b/src/eval/walk.rs index fe7f0e984..0898f20b9 100644 --- a/src/eval/walk.rs +++ b/src/eval/walk.rs @@ -126,12 +126,9 @@ fn walk_item(ctx: &mut EvalContext, label: EcoString, body: Template) { ctx.template += Template::from_block(move |style| { let label = Layout::pack(ParNode { dir: style.par.dir, + align: style.par.align, leading: style.leading(), - children: vec![ParChild::Text( - label.clone(), - style.aligns.inline, - Rc::clone(&style.text), - )], + children: vec![ParChild::Text(label.clone(), Rc::clone(&style.text))], }); let spacing = style.text.size / 2.0; diff --git a/src/geom/align.rs b/src/geom/align.rs index 599600848..b0cf69db8 100644 --- a/src/geom/align.rs +++ b/src/geom/align.rs @@ -3,18 +3,14 @@ use super::*; /// Where to align something along an axis. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum Align { - /// Align at the start of the axis. - Start, - /// Align in the middle of the axis. - Center, - /// Align at the end of the axis. - End, /// Align at the left side of the axis. Left, - /// Align at the right side of the axis. - Right, /// Align at the top side of the axis. Top, + /// Align in the middle of the axis. + Center, + /// Align at the right side of the axis. + Right, /// Align at the bottom side of the axis. Bottom, } @@ -23,12 +19,10 @@ impl Align { /// The axis this alignment belongs to if it is specific. pub fn axis(self) -> Option { match self { - Self::Start => None, - Self::Center => None, - Self::End => None, Self::Left => Some(SpecAxis::Horizontal), - Self::Right => Some(SpecAxis::Horizontal), Self::Top => Some(SpecAxis::Vertical), + Self::Center => None, + Self::Right => Some(SpecAxis::Horizontal), Self::Bottom => Some(SpecAxis::Vertical), } } @@ -36,60 +30,42 @@ impl Align { /// The inverse alignment. pub fn inv(self) -> Self { match self { - Self::Start => Self::End, - Self::Center => Self::Center, - Self::End => Self::Start, Self::Left => Self::Right, - Self::Right => Self::Left, Self::Top => Self::Bottom, + Self::Center => Self::Center, + Self::Right => Self::Left, Self::Bottom => Self::Top, } } /// Returns the position of this alignment in the given range. - pub fn resolve(self, dir: Dir, range: Range) -> Length { - #[cfg(debug_assertions)] - if let Some(axis) = self.axis() { - debug_assert_eq!(axis, dir.axis()) - } - + pub fn resolve(self, range: Range) -> Length { match self { - Self::Start => { - if dir.is_positive() { - range.start - } else { - range.end - } - } - Self::Center => (range.start + range.end) / 2.0, - Self::End => { - if dir.is_positive() { - range.end - } else { - range.start - } - } Self::Left | Self::Top => range.start, Self::Right | Self::Bottom => range.end, + Self::Center => (range.start + range.end) / 2.0, } } } -impl Default for Align { - fn default() -> Self { - Self::Start +impl From for Align { + fn from(side: Side) -> Self { + match side { + Side::Left => Self::Left, + Side::Top => Self::Top, + Side::Right => Self::Right, + Side::Bottom => Self::Bottom, + } } } impl Debug for Align { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.pad(match self { - Self::Start => "start", - Self::Center => "center", - Self::End => "end", Self::Left => "left", - Self::Right => "right", Self::Top => "top", + Self::Center => "center", + Self::Right => "right", Self::Bottom => "bottom", }) } diff --git a/src/geom/spec.rs b/src/geom/spec.rs index 4d631399f..3b49b54a1 100644 --- a/src/geom/spec.rs +++ b/src/geom/spec.rs @@ -23,6 +23,11 @@ impl Spec { Self { x: v.clone(), y: v } } + /// Borrows the individual fields. + pub fn as_ref(&self) -> Spec<&T> { + Spec { x: &self.x, y: &self.y } + } + /// Maps the individual fields with `f`. pub fn map(self, mut f: F) -> Spec where @@ -40,6 +45,14 @@ impl Spec { } } + /// Whether a condition is true for at least one of fields. + pub fn any(self, mut f: F) -> bool + where + F: FnMut(&T) -> bool, + { + f(&self.x) || f(&self.y) + } + /// Whether a condition is true for both fields. pub fn all(self, mut f: F) -> bool where diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 949fff642..33502fffa 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -10,15 +10,16 @@ pub use constraints::*; pub use incremental::*; pub use regions::*; +use std::any::Any; use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::rc::Rc; use crate::font::FontStore; use crate::frame::Frame; -use crate::geom::{Linear, Spec}; +use crate::geom::{Align, Linear, Spec}; use crate::image::ImageStore; -use crate::library::{DocumentNode, SizedNode}; +use crate::library::{AlignNode, DocumentNode, SizedNode}; use crate::Context; /// Layout a document node into a collection of frames. @@ -59,7 +60,7 @@ impl<'a> LayoutContext<'a> { /// /// Layout return one frame per used region alongside constraints that define /// whether the result is reusable in other regions. -pub trait Layout: Debug { +pub trait Layout { /// Layout the node into the given regions, producing constrained frames. fn layout( &self, @@ -70,12 +71,11 @@ pub trait Layout: Debug { /// Convert to a packed node. fn pack(self) -> PackedNode where - Self: Sized + Hash + 'static, + Self: Debug + Hash + Sized + 'static, { PackedNode { #[cfg(feature = "layout-cache")] hash: { - use std::any::Any; let mut state = fxhash::FxHasher64::default(); self.type_id().hash(&mut state); self.hash(&mut state); @@ -89,26 +89,40 @@ pub trait Layout: Debug { /// A packed layouting node with precomputed hash. #[derive(Clone)] pub struct PackedNode { - node: Rc, + node: Rc, #[cfg(feature = "layout-cache")] hash: u64, } impl PackedNode { + /// 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. - /// - /// If at least one of `width` and `height` is `Some`, this wraps the node - /// in a [`SizedNode`]. Otherwise, it returns the node unchanged. pub fn sized(self, width: Option, height: Option) -> PackedNode { if width.is_some() || height.is_some() { Layout::pack(SizedNode { - sizing: Spec::new(width, height), child: self, + sizing: Spec::new(width, height), }) } else { self } } + + /// Set alignments for this node. + pub fn aligned(self, x: Option, y: Option) -> PackedNode { + if x.is_some() || y.is_some() { + Layout::pack(AlignNode { child: self, aligns: Spec::new(x, y) }) + } else { + self + } + } } impl Layout for PackedNode { @@ -166,3 +180,16 @@ impl Debug for PackedNode { self.node.fmt(f) } } + +trait Bounds: Layout + Debug + 'static { + fn as_any(&self) -> &dyn Any; +} + +impl Bounds for T +where + T: Layout + Debug + 'static, +{ + fn as_any(&self) -> &dyn Any { + self + } +} diff --git a/src/library/align.rs b/src/library/align.rs index c6f96a136..591a40857 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -1,51 +1,77 @@ use super::prelude::*; /// `align`: Configure the alignment along the layouting axes. -pub fn align(ctx: &mut EvalContext, args: &mut Args) -> TypResult { - let first = args.find::(); - let second = args.find::(); - let body = args.find::