diff --git a/src/eval/class.rs b/src/eval/class.rs index 7888cba28..b7b4d0121 100644 --- a/src/eval/class.rs +++ b/src/eval/class.rs @@ -66,7 +66,7 @@ impl Class { let mut styles = StyleMap::new(); self.set(args, &mut styles)?; let node = (self.construct)(ctx, args)?; - Ok(node.styled(styles)) + Ok(node.styled_with_map(styles)) } /// Execute the class's set rule. diff --git a/src/eval/node.rs b/src/eval/node.rs index 2c955d012..37f230ee7 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -5,7 +5,7 @@ use std::iter::Sum; use std::mem; use std::ops::{Add, AddAssign}; -use super::{StyleMap, Styled}; +use super::{Property, StyleMap, Styled}; use crate::diag::StrResult; use crate::geom::SpecAxis; use crate::layout::{Layout, PackedNode, RootNode}; @@ -84,14 +84,33 @@ impl Node { Self::Block(node.pack()) } - /// Style this node. - pub fn styled(self, styles: StyleMap) -> Self { + /// Style this node with a single property. + pub fn styled(mut self, key: P, value: P::Value) -> Self { + if let Self::Sequence(vec) = &mut self { + if let [styled] = vec.as_mut_slice() { + styled.map.set(key, value); + return self; + } + } + + self.styled_with_map(StyleMap::with(key, value)) + } + + /// Style this node with a full style map. + pub fn styled_with_map(mut self, styles: StyleMap) -> Self { + if let Self::Sequence(vec) = &mut self { + if let [styled] = vec.as_mut_slice() { + styled.map.apply(&styles); + return self; + } + } + Self::Sequence(vec![Styled::new(self, styles)]) } /// Style this node in monospace. pub fn monospaced(self) -> Self { - self.styled(StyleMap::with(TextNode::MONOSPACE, true)) + self.styled(TextNode::MONOSPACE, true) } /// Lift to a type-erased block-level node. diff --git a/src/eval/styles.rs b/src/eval/styles.rs index 54ac2697f..0f3d694ab 100644 --- a/src/eval/styles.rs +++ b/src/eval/styles.rs @@ -106,7 +106,6 @@ impl StyleMap { /// `outer`. The ones from `self` take precedence over the ones from /// `outer`. For folded properties `self` contributes the inner value. pub fn chain<'a>(&'a self, outer: &'a StyleChain<'a>) -> StyleChain<'a> { - // No need to chain an empty map. if self.is_empty() { *outer } else { @@ -182,7 +181,9 @@ impl PartialEq for StyleMap { /// matches further up the chain. #[derive(Clone, Copy, Hash)] pub struct StyleChain<'a> { + /// The first map in the chain. inner: &'a StyleMap, + /// The remaining maps in the chain. outer: Option<&'a Self>, } @@ -238,14 +239,13 @@ impl<'a> StyleChain<'a> { /// entry for it. pub fn get_cloned(self, key: P) -> P::Value { if let Some(value) = self.find(key).cloned() { - if P::FOLDABLE { - if let Some(outer) = self.outer { - P::fold(value, outer.get_cloned(key)) - } else { - P::fold(value, P::default()) - } - } else { - value + if !P::FOLDABLE { + return value; + } + + match self.outer { + Some(outer) => P::fold(value, outer.get_cloned(key)), + None => P::fold(value, P::default()), } } else if let Some(outer) = self.outer { outer.get_cloned(key) diff --git a/src/library/link.rs b/src/library/link.rs index 1050963c6..b7e3d587f 100644 --- a/src/library/link.rs +++ b/src/library/link.rs @@ -15,9 +15,9 @@ pub fn link(_: &mut EvalContext, args: &mut Args) -> TypResult { Node::Text(text.into()) }); - let mut passed = StyleMap::new(); - passed.set(TextNode::LINK, Some(url.clone())); - passed.set(ImageNode::LINK, Some(url.clone())); - passed.set(ShapeNode::LINK, Some(url)); - Ok(Value::Node(body.styled(passed))) + let mut map = StyleMap::new(); + map.set(TextNode::LINK, Some(url.clone())); + map.set(ImageNode::LINK, Some(url.clone())); + map.set(ShapeNode::LINK, Some(url)); + Ok(Value::Node(body.styled_with_map(map))) } diff --git a/src/library/mod.rs b/src/library/mod.rs index 89f4ab9d6..461716a17 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -61,8 +61,8 @@ prelude! { pub use crate::diag::{At, TypResult}; pub use crate::eval::{ - Args, Construct, EvalContext, Node, Property, Set, Smart, StyleChain, StyleMap, - Styled, Value, + Args, Construct, EvalContext, Node, Property, Scope, Set, Smart, StyleChain, + StyleMap, Styled, Value, }; pub use crate::frame::*; pub use crate::geom::*; @@ -73,53 +73,50 @@ prelude! { pub use crate::util::{EcoString, OptionExt}; } -use crate::eval::Scope; use prelude::*; /// Construct a scope containing all standard library definitions. pub fn new() -> Scope { let mut std = Scope::new(); - // Classes. + // Structure and semantics. std.def_class::("page"); std.def_class::("par"); std.def_class::("text"); - std.def_class::("heading"); - std.def_class::>("list"); - std.def_class::>("enum"); - - // Text functions. std.def_func("underline", underline); std.def_func("strike", strike); std.def_func("overline", overline); std.def_func("link", link); - - // Break and spacing functions. - std.def_func("pagebreak", pagebreak); - std.def_func("colbreak", colbreak); - std.def_func("parbreak", parbreak); - std.def_func("linebreak", linebreak); - std.def_func("h", h); - std.def_func("v", v); - - // Layout functions. - std.def_func("box", box_); - std.def_func("block", block); - std.def_func("stack", stack); - std.def_func("grid", grid); - std.def_func("pad", pad); - std.def_func("columns", columns); - std.def_func("align", align); - std.def_func("place", place); - std.def_func("move", move_); - std.def_func("scale", scale); - std.def_func("rotate", rotate); + std.def_class::("heading"); + std.def_class::>("list"); + std.def_class::>("enum"); std.def_func("image", image); std.def_func("rect", rect); std.def_func("square", square); std.def_func("ellipse", ellipse); std.def_func("circle", circle); + // Layout. + std.def_func("h", h); + std.def_func("v", v); + std.def_func("box", box_); + std.def_func("block", block); + std.def_func("align", align); + std.def_func("pad", pad); + std.def_func("place", place); + std.def_func("move", move_); + std.def_func("scale", scale); + std.def_func("rotate", rotate); + std.def_func("stack", stack); + std.def_func("grid", grid); + std.def_func("columns", columns); + + // Breaks. + std.def_func("pagebreak", pagebreak); + std.def_func("colbreak", colbreak); + std.def_func("parbreak", parbreak); + std.def_func("linebreak", linebreak); + // Utility functions. std.def_func("assert", assert); std.def_func("type", type_); diff --git a/src/library/page.rs b/src/library/page.rs index a6d489ba4..522fd3ac0 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Display, Formatter}; use std::str::FromStr; use super::prelude::*; -use super::{ColumnsNode, PadNode}; +use super::ColumnsNode; /// Layouts its child onto one or multiple pages. #[derive(Clone, PartialEq, Hash)] @@ -111,7 +111,7 @@ impl PageNode { } // Realize margins with padding node. - child = PadNode { child, padding }.pack(); + child = child.padded(padding); // Layout the child. let expand = size.map(Length::is_finite); diff --git a/src/library/par.rs b/src/library/par.rs index 87ad2ebec..38d150979 100644 --- a/src/library/par.rs +++ b/src/library/par.rs @@ -29,7 +29,10 @@ impl ParNode { impl Construct for ParNode { fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult { - // Lift to a block so that it doesn't merge with adjacent stuff. + // The paragraph constructor is special: It doesn't create a paragraph + // since that happens automatically through markup. Instead, it just + // lifts the passed body to the block level so that it won't merge with + // adjacent stuff and it styles the contained paragraphs. Ok(Node::Block(args.expect("body")?)) } } diff --git a/src/library/text.rs b/src/library/text.rs index ab5c15c31..d5c87949b 100644 --- a/src/library/text.rs +++ b/src/library/text.rs @@ -96,10 +96,10 @@ impl TextNode { impl Construct for TextNode { fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult { - // We don't need to do anything more here because the whole point of the - // text constructor is to apply the styles and that happens - // automatically during class construction. - args.expect::("body") + // The text constructor is special: It doesn't create a text node. + // Instead, it leaves the passed argument structurally unchanged, but + // styles all text in it. + args.expect("body") } } @@ -404,9 +404,7 @@ fn line_impl(args: &mut Args, kind: LineKind) -> TypResult { let extent = args.named("extent")?.unwrap_or_default(); let body: Node = args.expect("body")?; let deco = LineDecoration { kind, stroke, thickness, offset, extent }; - Ok(Value::Node( - body.styled(StyleMap::with(TextNode::LINES, vec![deco])), - )) + Ok(Value::Node(body.styled(TextNode::LINES, vec![deco]))) } /// Defines a line that is positioned over, under or on top of text. diff --git a/tests/typ/style/set-site.typ b/tests/typ/style/set-site.typ index 0a00e1999..b5464aebc 100644 --- a/tests/typ/style/set-site.typ +++ b/tests/typ/style/set-site.typ @@ -12,8 +12,7 @@ Hello *{x}* #let fruit = [ - Apple - Orange - #set list(body-indent: 10pt) - - Pear + #list(body-indent: 10pt, [Pear]) ] - Fruit