diff options
Diffstat (limited to 'src/eval')
| -rw-r--r-- | src/eval/mod.rs | 32 | ||||
| -rw-r--r-- | src/eval/node.rs | 213 | ||||
| -rw-r--r-- | src/eval/scope.rs | 1 | ||||
| -rw-r--r-- | src/eval/styles.rs | 212 | ||||
| -rw-r--r-- | src/eval/value.rs | 7 |
5 files changed, 388 insertions, 77 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs index c1f0b024..d5b33280 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -117,14 +117,16 @@ impl<'a> EvalContext<'a> { // Prepare the new context. let new_scopes = Scopes::new(self.scopes.base); - let old_scopes = mem::replace(&mut self.scopes, new_scopes); + let prev_scopes = mem::replace(&mut self.scopes, new_scopes); + let prev_styles = mem::take(&mut self.styles); self.route.push(id); // Evaluate the module. let template = ast.eval(self).trace(|| Tracepoint::Import, span)?; // Restore the old context. - let new_scopes = mem::replace(&mut self.scopes, old_scopes); + let new_scopes = mem::replace(&mut self.scopes, prev_scopes); + self.styles = prev_styles; self.route.pop().unwrap(); // Save the evaluated module. @@ -160,11 +162,13 @@ impl Eval for Markup { type Output = Node; fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { - let mut result = Node::new(); + let prev = mem::take(&mut ctx.styles); + let mut seq = vec![]; for piece in self.nodes() { - result += piece.eval(ctx)?; + seq.push((piece.eval(ctx)?, ctx.styles.clone())); } - Ok(result) + ctx.styles = prev; + Ok(Node::Sequence(seq)) } } @@ -190,7 +194,7 @@ impl Eval for MarkupNode { Self::Heading(heading) => heading.eval(ctx)?, Self::List(list) => list.eval(ctx)?, Self::Enum(enum_) => enum_.eval(ctx)?, - Self::Expr(expr) => expr.eval(ctx)?.display(), + Self::Expr(expr) => expr.eval(ctx)?.show(), }) } } @@ -199,8 +203,7 @@ impl Eval for RawNode { type Output = Node; fn eval(&self, _: &mut EvalContext) -> TypResult<Self::Output> { - // TODO(set): Styled in monospace. - let text = Node::Text(self.text.clone()); + let text = Node::Text(self.text.clone()).monospaced(); Ok(if self.block { Node::Block(text.into_block()) } else { @@ -213,8 +216,7 @@ impl Eval for MathNode { type Output = Node; fn eval(&self, _: &mut EvalContext) -> TypResult<Self::Output> { - // TODO(set): Styled in monospace. - let text = Node::Text(self.formula.clone()); + let text = Node::Text(self.formula.clone()).monospaced(); Ok(if self.display { Node::Block(text.into_block()) } else { @@ -227,8 +229,14 @@ impl Eval for HeadingNode { type Output = Node; fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { - // TODO(set): Styled appropriately. - Ok(Node::Block(self.body().eval(ctx)?.into_block())) + // TODO(set): Relative font size. + let upscale = (1.6 - 0.1 * self.level() as f64).max(0.75); + let mut styles = Styles::new(); + styles.set(TextNode::STRONG, true); + styles.set(TextNode::SIZE, upscale * Length::pt(11.0)); + Ok(Node::Block( + self.body().eval(ctx)?.into_block().styled(styles), + )) } } diff --git a/src/eval/node.rs b/src/eval/node.rs index 4d259e2d..d3bf9806 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -1,9 +1,10 @@ use std::convert::TryFrom; -use std::fmt::{self, Debug, Formatter}; +use std::fmt::Debug; use std::hash::Hash; use std::mem; use std::ops::{Add, AddAssign}; +use super::Styles; use crate::diag::StrResult; use crate::geom::SpecAxis; use crate::layout::{Layout, PackedNode}; @@ -16,8 +17,9 @@ use crate::util::EcoString; /// A partial representation of a layout node. /// /// A node is a composable intermediate representation that can be converted -/// into a proper layout node by lifting it to the block or page level. -#[derive(Clone)] +/// into a proper layout node by lifting it to a block-level or document node. +// TODO(set): Fix Debug impl leaking into user-facing repr. +#[derive(Debug, Clone)] pub enum Node { /// A word space. Space, @@ -36,13 +38,13 @@ pub enum Node { /// A block node. Block(PackedNode), /// A sequence of nodes (which may themselves contain sequences). - Seq(Vec<Self>), + Sequence(Vec<(Self, Styles)>), } impl Node { /// Create an empty node. pub fn new() -> Self { - Self::Seq(vec![]) + Self::Sequence(vec![]) } /// Create an inline-level node. @@ -61,8 +63,22 @@ impl Node { Self::Block(node.pack()) } - /// Decoration this node. - pub fn decorate(self, _: Decoration) -> Self { + /// Style this node. + pub fn styled(self, styles: Styles) -> Self { + match self { + Self::Inline(inline) => Self::Inline(inline.styled(styles)), + Self::Block(block) => Self::Block(block.styled(styles)), + other => Self::Sequence(vec![(other, styles)]), + } + } + + /// Style this node in monospace. + pub fn monospaced(self) -> Self { + self.styled(Styles::one(TextNode::MONOSPACE, true)) + } + + /// Decorate this node. + pub fn decorated(self, _: Decoration) -> Self { // TODO(set): Actually decorate. self } @@ -73,7 +89,7 @@ impl Node { packed } else { let mut packer = NodePacker::new(); - packer.walk(self); + packer.walk(self, Styles::new()); packer.into_block() } } @@ -81,7 +97,7 @@ impl Node { /// Lift to a document node, the root of the layout tree. pub fn into_document(self) -> DocumentNode { let mut packer = NodePacker::new(); - packer.walk(self); + packer.walk(self, Styles::new()); packer.into_document() } @@ -91,13 +107,7 @@ impl Node { .map_err(|_| format!("cannot repeat this template {} times", n))?; // TODO(set): Make more efficient. - Ok(Self::Seq(vec![self.clone(); count])) - } -} - -impl Debug for Node { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("<node>") + Ok(Self::Sequence(vec![(self.clone(), Styles::new()); count])) } } @@ -119,7 +129,7 @@ impl Add for Node { fn add(self, rhs: Self) -> Self::Output { // TODO(set): Make more efficient. - Self::Seq(vec![self, rhs]) + Self::Sequence(vec![(self, Styles::new()), (rhs, Styles::new())]) } } @@ -129,86 +139,211 @@ impl AddAssign for Node { } } -/// Packs a `Node` into a flow or whole document. +/// Packs a [`Node`] into a flow or whole document. struct NodePacker { + /// The accumulated page nodes. document: Vec<PageNode>, + /// The common style properties of all items on the current page. + page_styles: Styles, + /// The accumulated flow children. flow: Vec<FlowChild>, + /// The accumulated paragraph children. par: Vec<ParChild>, + /// The common style properties of all items in the current paragraph. + par_styles: Styles, + /// The kind of thing that was last added to the current paragraph. + last: Last, +} + +/// The type of the last thing that was pushed into the paragraph. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +enum Last { + None, + Spacing, + Newline, + Space, + Other, } impl NodePacker { + /// Start a new node-packing session. fn new() -> Self { Self { document: vec![], + page_styles: Styles::new(), flow: vec![], par: vec![], + par_styles: Styles::new(), + last: Last::None, } } + /// Finish up and return the resulting flow. fn into_block(mut self) -> PackedNode { self.parbreak(); FlowNode(self.flow).pack() } + /// Finish up and return the resulting document. fn into_document(mut self) -> DocumentNode { - self.parbreak(); - self.pagebreak(); + self.pagebreak(true); DocumentNode(self.document) } - fn walk(&mut self, node: Node) { + /// Consider a node with the given styles. + fn walk(&mut self, node: Node, styles: Styles) { match node { Node::Space => { - self.push_inline(ParChild::Text(TextNode(' '.into()))); + // Only insert a space if the previous thing was actual content. + if self.last == Last::Other { + self.push_text(' '.into(), styles); + self.last = Last::Space; + } } Node::Linebreak => { - self.push_inline(ParChild::Text(TextNode('\n'.into()))); + self.trim(); + self.push_text('\n'.into(), styles); + self.last = Last::Newline; } Node::Parbreak => { self.parbreak(); } Node::Pagebreak => { - self.pagebreak(); + self.pagebreak(true); + self.page_styles = styles; } Node::Text(text) => { - self.push_inline(ParChild::Text(TextNode(text))); + self.push_text(text, styles); + } + Node::Spacing(SpecAxis::Horizontal, amount) => { + self.push_inline(ParChild::Spacing(amount), styles); + self.last = Last::Spacing; + } + Node::Spacing(SpecAxis::Vertical, amount) => { + self.push_block(FlowChild::Spacing(amount), styles); } - Node::Spacing(axis, amount) => match axis { - SpecAxis::Horizontal => self.push_inline(ParChild::Spacing(amount)), - SpecAxis::Vertical => self.push_block(FlowChild::Spacing(amount)), - }, Node::Inline(inline) => { - self.push_inline(ParChild::Node(inline)); + self.push_inline(ParChild::Node(inline), styles); } Node::Block(block) => { - self.push_block(FlowChild::Node(block)); + self.push_block(FlowChild::Node(block), styles); } - Node::Seq(list) => { - for node in list { - self.walk(node); + Node::Sequence(list) => { + for (node, mut inner) in list { + inner.apply(&styles); + self.walk(node, inner); } } } } + /// Remove a trailing space. + fn trim(&mut self) { + if self.last == Last::Space { + self.par.pop(); + self.last = Last::Other; + } + } + + /// Advance to the next paragraph. fn parbreak(&mut self) { + self.trim(); + let children = mem::take(&mut self.par); + let styles = mem::take(&mut self.par_styles); if !children.is_empty() { - self.flow.push(FlowChild::Node(ParNode(children).pack())); + // The paragraph's children are all compatible with the page, so the + // paragraph is too, meaning we don't need to check or intersect + // anything here. + let node = ParNode(children).pack().styled(styles); + self.flow.push(FlowChild::Node(node)); } + + self.last = Last::None; } - fn pagebreak(&mut self) { + /// Advance to the next page. + fn pagebreak(&mut self, keep: bool) { + self.parbreak(); let children = mem::take(&mut self.flow); - self.document.push(PageNode(FlowNode(children).pack())); + let styles = mem::take(&mut self.page_styles); + if keep || !children.is_empty() { + let node = PageNode { node: FlowNode(children).pack(), styles }; + self.document.push(node); + } + } + + /// Insert text into the current paragraph. + fn push_text(&mut self, text: EcoString, styles: Styles) { + // TODO(set): Join compatible text nodes. Take care with space + // coalescing. + let node = TextNode { text, styles: Styles::new() }; + self.push_inline(ParChild::Text(node), styles); } - fn push_inline(&mut self, child: ParChild) { + /// Insert an inline-level element into the current paragraph. + fn push_inline(&mut self, mut child: ParChild, styles: Styles) { + match &mut child { + ParChild::Spacing(_) => {} + ParChild::Text(node) => node.styles.apply(&styles), + ParChild::Node(node) => node.styles.apply(&styles), + ParChild::Decorate(_) => {} + ParChild::Undecorate => {} + } + + // The node must be both compatible with the current page and the + // current paragraph. + self.make_page_compatible(&styles); + self.make_par_compatible(&styles); self.par.push(child); + self.last = Last::Other; } - fn push_block(&mut self, child: FlowChild) { + /// Insert a block-level element into the current flow. + fn push_block(&mut self, mut child: FlowChild, styles: Styles) { self.parbreak(); + + match &mut child { + FlowChild::Spacing(_) => {} + FlowChild::Node(node) => node.styles.apply(&styles), + } + + // The node must be compatible with the current page. + self.make_page_compatible(&styles); self.flow.push(child); } + + /// Break to a new paragraph if the `styles` contain paragraph styles that + /// are incompatible with the current paragraph. + fn make_par_compatible(&mut self, styles: &Styles) { + if self.par.is_empty() { + self.par_styles = styles.clone(); + return; + } + + if !self.par_styles.compatible(&styles, ParNode::has_property) { + self.parbreak(); + self.par_styles = styles.clone(); + return; + } + + self.par_styles.intersect(&styles); + } + + /// Break to a new page if the `styles` contain page styles that are + /// incompatible with the current page. + fn make_page_compatible(&mut self, styles: &Styles) { + if self.flow.is_empty() && self.par.is_empty() { + self.page_styles = styles.clone(); + return; + } + + if !self.page_styles.compatible(&styles, PageNode::has_property) { + self.pagebreak(false); + self.page_styles = styles.clone(); + return; + } + + self.page_styles.intersect(styles); + } } diff --git a/src/eval/scope.rs b/src/eval/scope.rs index 2290affd..ffe2d63e 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -120,6 +120,7 @@ impl Scope { impl Debug for Scope { fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_str("Scope ")?; f.debug_map() .entries(self.values.iter().map(|(k, v)| (k, v.borrow()))) .finish() diff --git a/src/eval/styles.rs b/src/eval/styles.rs index 30822edb..9d204843 100644 --- a/src/eval/styles.rs +++ b/src/eval/styles.rs @@ -1,6 +1,6 @@ use std::any::{Any, TypeId}; -use std::collections::HashMap; use std::fmt::{self, Debug, Formatter}; +use std::hash::{Hash, Hasher}; use std::rc::Rc; // Possible optimizations: @@ -9,20 +9,48 @@ use std::rc::Rc; // - Store small properties inline /// A map of style properties. -#[derive(Default, Clone)] +#[derive(Default, Clone, Hash)] pub struct Styles { - map: HashMap<TypeId, Rc<dyn Any>>, + pub(crate) map: Vec<(StyleId, Entry)>, } impl Styles { /// Create a new, empty style map. pub fn new() -> Self { - Self { map: HashMap::new() } + Self { map: vec![] } + } + + /// Create a style map with a single property-value pair. + pub fn one<P: Property>(key: P, value: P::Value) -> Self + where + P::Value: Debug + Hash + PartialEq + 'static, + { + let mut styles = Self::new(); + styles.set(key, value); + styles + } + + /// Whether this map contains no styles. + pub fn is_empty(&self) -> bool { + self.map.is_empty() } /// Set the value for a style property. - pub fn set<P: Property>(&mut self, _: P, value: P::Value) { - self.map.insert(TypeId::of::<P>(), Rc::new(value)); + pub fn set<P: Property>(&mut self, key: P, value: P::Value) + where + P::Value: Debug + Hash + PartialEq + 'static, + { + let id = StyleId::of::<P>(); + let entry = Entry::new(key, value); + + for pair in &mut self.map { + if pair.0 == id { + pair.1 = entry; + return; + } + } + + self.map.push((id, entry)); } /// Get the value of a copyable style property. @@ -33,7 +61,7 @@ impl Styles { where P::Value: Copy, { - self.get_inner(key).copied().unwrap_or_else(P::default) + self.get_direct(key).copied().unwrap_or_else(P::default) } /// Get a reference to a style property. @@ -41,30 +69,147 @@ impl Styles { /// Returns a reference to the property's default value if the map does not /// contain an entry for it. pub fn get_ref<P: Property>(&self, key: P) -> &P::Value { - self.get_inner(key).unwrap_or_else(|| P::default_ref()) + self.get_direct(key).unwrap_or_else(|| P::default_ref()) } - /// Get a reference to a style directly in this map. - fn get_inner<P: Property>(&self, _: P) -> Option<&P::Value> { + /// Get a reference to a style directly in this map (no default value). + pub fn get_direct<P: Property>(&self, _: P) -> Option<&P::Value> { self.map - .get(&TypeId::of::<P>()) - .and_then(|boxed| boxed.downcast_ref()) + .iter() + .find(|pair| pair.0 == StyleId::of::<P>()) + .and_then(|pair| pair.1.downcast()) + } + + /// Apply styles from `outer` in-place. + /// + /// Properties from `self` take precedence over the ones from `outer`. + pub fn apply(&mut self, outer: &Self) { + for pair in &outer.map { + if self.map.iter().all(|&(id, _)| pair.0 != id) { + self.map.push(pair.clone()); + } + } + } + + /// Create new styles combining `self` with `outer`. + /// + /// Properties from `self` take precedence over the ones from `outer`. + pub fn chain(&self, outer: &Self) -> Self { + let mut styles = self.clone(); + styles.apply(outer); + styles + } + + /// Keep only those styles that are also in `other`. + pub fn intersect(&mut self, other: &Self) { + self.map.retain(|a| other.map.iter().any(|b| a == b)); + } + + /// Whether two style maps are equal when filtered down to the given + /// properties. + pub fn compatible<F>(&self, other: &Self, filter: F) -> bool + where + F: Fn(StyleId) -> bool, + { + let f = |e: &&(StyleId, Entry)| filter(e.0); + self.map.iter().filter(f).all(|pair| other.map.contains(pair)) + && other.map.iter().filter(f).all(|pair| self.map.contains(pair)) } } impl Debug for Styles { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - // TODO(set): Better debug printing possible? - f.pad("Styles(..)") + f.write_str("Styles ")?; + f.debug_set().entries(self.map.iter().map(|pair| &pair.1)).finish() + } +} + +/// An entry for a single style property. +#[derive(Clone)] +pub(crate) struct Entry { + #[cfg(debug_assertions)] + name: &'static str, + value: Rc<dyn Bounds>, +} + +impl Entry { + fn new<P: Property>(_: P, value: P::Value) -> Self + where + P::Value: Debug + Hash + PartialEq + 'static, + { + Self { + #[cfg(debug_assertions)] + name: P::NAME, + value: Rc::new(value), + } + } + + fn downcast<T: 'static>(&self) -> Option<&T> { + self.value.as_any().downcast_ref() + } +} + +impl Debug for Entry { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + #[cfg(debug_assertions)] + write!(f, "{}: ", self.name)?; + write!(f, "{:?}", &self.value) + } +} + +impl PartialEq for Entry { + fn eq(&self, other: &Self) -> bool { + self.value.dyn_eq(other) + } +} + +impl Hash for Entry { + fn hash<H: Hasher>(&self, state: &mut H) { + state.write_u64(self.value.hash64()); + } +} + +trait Bounds: Debug + 'static { + fn as_any(&self) -> &dyn Any; + fn dyn_eq(&self, other: &Entry) -> bool; + fn hash64(&self) -> u64; +} + +impl<T> Bounds for T +where + T: Debug + Hash + PartialEq + 'static, +{ + fn as_any(&self) -> &dyn Any { + self + } + + fn dyn_eq(&self, other: &Entry) -> bool { + if let Some(other) = other.downcast::<Self>() { + self == other + } else { + false + } + } + + fn hash64(&self) -> u64 { + // No need to hash the TypeId since there's only one + // valid value type per property. + fxhash::hash64(self) } } -/// Stylistic property keys. +/// Style property keys. +/// +/// This trait is not intended to be implemented manually, but rather through +/// the `properties!` macro. pub trait Property: 'static { /// The type of this property, for example, this could be /// [`Length`](crate::geom::Length) for a `WIDTH` property. type Value; + /// The name of the property, used for debug printing. + const NAME: &'static str; + /// The default value of the property. fn default() -> Self::Value; @@ -76,14 +221,18 @@ pub trait Property: 'static { fn default_ref() -> &'static Self::Value; } -macro_rules! set { - ($ctx:expr, $target:expr => $source:expr) => { - if let Some(v) = $source { - $ctx.styles.set($target, v); - } - }; +/// A unique identifier for a style property. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct StyleId(TypeId); + +impl StyleId { + /// The style id of the property. + pub fn of<P: Property>() -> Self { + Self(TypeId::of::<P>()) + } } +/// Generate the property keys for a node. macro_rules! properties { ($node:ty, $( $(#[$attr:meta])* @@ -92,10 +241,10 @@ macro_rules! properties { // TODO(set): Fix possible name clash. mod properties { use std::marker::PhantomData; + use $crate::eval::{Property, StyleId}; use super::*; $(#[allow(non_snake_case)] mod $name { - use $crate::eval::Property; use once_cell::sync::Lazy; use super::*; @@ -104,6 +253,10 @@ macro_rules! properties { impl Property for Key<$type> { type Value = $type; + const NAME: &'static str = concat!( + stringify!($node), "::", stringify!($name) + ); + fn default() -> Self::Value { $default } @@ -116,9 +269,24 @@ macro_rules! properties { })* impl $node { + /// Check whether the property with the given type id belongs to + /// `Self`. + pub fn has_property(id: StyleId) -> bool { + false || $(id == StyleId::of::<$name::Key<$type>>())||* + } + $($(#[$attr])* pub const $name: $name::Key<$type> = $name::Key(PhantomData);)* } } }; } + +/// Set a style property to a value if the value is `Some`. +macro_rules! set { + ($ctx:expr, $target:expr => $value:expr) => { + if let Some(v) = $value { + $ctx.styles.set($target, v); + } + }; +} diff --git a/src/eval/value.rs b/src/eval/value.rs index a6230956..c2a284eb 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -108,8 +108,8 @@ impl Value { format_eco!("{:?}", self) } - /// Return the display representation of a value in form of a node. - pub fn display(self) -> Node { + /// Return the display representation of the value. + pub fn show(self) -> Node { match self { Value::None => Node::new(), Value::Int(v) => Node::Text(format_eco!("{}", v)), @@ -118,8 +118,7 @@ impl Value { Value::Node(v) => v, // For values which can't be shown "naturally", we print the // representation in monospace. - // TODO(set): Styled in monospace. - v => Node::Text(v.repr()), + v => Node::Text(v.repr()).monospaced(), } } } |
