summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-12-15 11:11:57 +0100
committerLaurenz <laurmaedje@gmail.com>2021-12-15 11:11:57 +0100
commitae38be9097bbb32142ef776e77e627ac12379000 (patch)
treef365a348d4c77d2d607d37fee3bc65a601d00a64 /src/eval
parentfe21c4d399d291e75165b664762f0aa8bdc4724a (diff)
Set Rules Episode IV: A New Fold
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/mod.rs11
-rw-r--r--src/eval/node.rs289
-rw-r--r--src/eval/styles.rs161
-rw-r--r--src/eval/value.rs10
4 files changed, 293 insertions, 178 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index d5b33280..6dcff900 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -181,11 +181,11 @@ impl Eval for MarkupNode {
Self::Linebreak => Node::Linebreak,
Self::Parbreak => Node::Parbreak,
Self::Strong => {
- ctx.styles.set(TextNode::STRONG, !ctx.styles.get(TextNode::STRONG));
+ ctx.styles.toggle(TextNode::STRONG);
Node::new()
}
Self::Emph => {
- ctx.styles.set(TextNode::EMPH, !ctx.styles.get(TextNode::EMPH));
+ ctx.styles.toggle(TextNode::EMPH);
Node::new()
}
Self::Text(text) => Node::Text(text.clone()),
@@ -216,7 +216,7 @@ impl Eval for MathNode {
type Output = Node;
fn eval(&self, _: &mut EvalContext) -> TypResult<Self::Output> {
- let text = Node::Text(self.formula.clone()).monospaced();
+ let text = Node::Text(self.formula.trim().into()).monospaced();
Ok(if self.display {
Node::Block(text.into_block())
} else {
@@ -229,11 +229,10 @@ impl Eval for HeadingNode {
type Output = Node;
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
- // 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));
+ styles.set(TextNode::SIZE, Relative::new(upscale).into());
Ok(Node::Block(
self.body().eval(ctx)?.into_block().styled(styles),
))
@@ -266,7 +265,7 @@ fn labelled(_: &mut EvalContext, label: EcoString, body: Node) -> TypResult<Node
// TODO: Switch to em units for gutter once available.
Ok(Node::block(GridNode {
tracks: Spec::new(vec![TrackSizing::Auto; 2], vec![]),
- gutter: Spec::new(vec![TrackSizing::Linear(Length::pt(6.0).into())], vec![]),
+ gutter: Spec::new(vec![TrackSizing::Linear(Length::pt(5.0).into())], vec![]),
children: vec![Node::Text(label).into_block(), body.into_block()],
}))
}
diff --git a/src/eval/node.rs b/src/eval/node.rs
index d3bf9806..52d9b244 100644
--- a/src/eval/node.rs
+++ b/src/eval/node.rs
@@ -9,8 +9,8 @@ use crate::diag::StrResult;
use crate::geom::SpecAxis;
use crate::layout::{Layout, PackedNode};
use crate::library::{
- Decoration, DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, Spacing,
- TextNode,
+ DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, PlacedNode,
+ SpacingKind, SpacingNode, TextNode,
};
use crate::util::EcoString;
@@ -18,8 +18,7 @@ use crate::util::EcoString;
///
/// A node is a composable intermediate representation that can be converted
/// 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)]
+#[derive(Debug, PartialEq, Clone, Hash)]
pub enum Node {
/// A word space.
Space,
@@ -32,7 +31,7 @@ pub enum Node {
/// Plain text.
Text(EcoString),
/// Spacing.
- Spacing(SpecAxis, Spacing),
+ Spacing(SpecAxis, SpacingKind),
/// An inline node.
Inline(PackedNode),
/// A block node.
@@ -77,18 +76,12 @@ impl Node {
self.styled(Styles::one(TextNode::MONOSPACE, true))
}
- /// Decorate this node.
- pub fn decorated(self, _: Decoration) -> Self {
- // TODO(set): Actually decorate.
- self
- }
-
/// Lift to a type-erased block-level node.
pub fn into_block(self) -> PackedNode {
if let Node::Block(packed) = self {
packed
} else {
- let mut packer = NodePacker::new();
+ let mut packer = NodePacker::new(true);
packer.walk(self, Styles::new());
packer.into_block()
}
@@ -96,7 +89,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();
+ let mut packer = NodePacker::new(false);
packer.walk(self, Styles::new());
packer.into_document()
}
@@ -117,13 +110,6 @@ impl Default for Node {
}
}
-impl PartialEq for Node {
- fn eq(&self, _: &Self) -> bool {
- // TODO(set): Figure out what to do here.
- false
- }
-}
-
impl Add for Node {
type Output = Self;
@@ -141,92 +127,89 @@ impl AddAssign for Node {
/// Packs a [`Node`] into a flow or whole document.
struct NodePacker {
+ /// Whether packing should produce a block-level node.
+ block: bool,
/// The accumulated page nodes.
- document: Vec<PageNode>,
- /// The common style properties of all items on the current page.
- page_styles: Styles,
+ pages: Vec<PageNode>,
/// The accumulated flow children.
flow: Vec<FlowChild>,
+ /// The common style properties of all items on the current flow.
+ flow_styles: Styles,
+ /// The kind of thing that was last added to the current flow.
+ flow_last: Last<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,
+ par_last: Last<ParChild>,
}
impl NodePacker {
/// Start a new node-packing session.
- fn new() -> Self {
+ fn new(block: bool) -> Self {
Self {
- document: vec![],
- page_styles: Styles::new(),
+ block,
+ pages: vec![],
flow: vec![],
+ flow_styles: Styles::new(),
+ flow_last: Last::None,
par: vec![],
par_styles: Styles::new(),
- last: Last::None,
+ par_last: Last::None,
}
}
/// Finish up and return the resulting flow.
fn into_block(mut self) -> PackedNode {
- self.parbreak();
+ self.finish_par();
FlowNode(self.flow).pack()
}
/// Finish up and return the resulting document.
fn into_document(mut self) -> DocumentNode {
self.pagebreak(true);
- DocumentNode(self.document)
+ DocumentNode(self.pages)
}
/// Consider a node with the given styles.
fn walk(&mut self, node: Node, styles: Styles) {
match node {
Node::Space => {
- // 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;
+ if self.is_flow_compatible(&styles) && self.is_par_compatible(&styles) {
+ self.par_last.soft(ParChild::text(' ', styles));
}
}
Node::Linebreak => {
- self.trim();
- self.push_text('\n'.into(), styles);
- self.last = Last::Newline;
+ self.par_last.hard();
+ self.push_inline(ParChild::text('\n', styles));
+ self.par_last.hard();
}
Node::Parbreak => {
- self.parbreak();
+ self.parbreak(Some(styles));
}
Node::Pagebreak => {
self.pagebreak(true);
- self.page_styles = styles;
+ self.flow_styles = styles;
}
Node::Text(text) => {
- self.push_text(text, styles);
+ self.push_inline(ParChild::text(text, styles));
}
- Node::Spacing(SpecAxis::Horizontal, amount) => {
- self.push_inline(ParChild::Spacing(amount), styles);
- self.last = Last::Spacing;
+ Node::Spacing(SpecAxis::Horizontal, kind) => {
+ self.par_last.hard();
+ self.push_inline(ParChild::Spacing(SpacingNode { kind, styles }));
+ self.par_last.hard();
}
- Node::Spacing(SpecAxis::Vertical, amount) => {
- self.push_block(FlowChild::Spacing(amount), styles);
+ Node::Spacing(SpecAxis::Vertical, kind) => {
+ self.finish_par();
+ self.flow.push(FlowChild::Spacing(SpacingNode { kind, styles }));
+ self.flow_last.hard();
}
Node::Inline(inline) => {
- self.push_inline(ParChild::Node(inline), styles);
+ self.push_inline(ParChild::Node(inline.styled(styles)));
}
Node::Block(block) => {
- self.push_block(FlowChild::Node(block), styles);
+ self.push_block(block.styled(styles));
}
Node::Sequence(list) => {
for (node, mut inner) in list {
@@ -237,80 +220,118 @@ impl NodePacker {
}
}
- /// Remove a trailing space.
- fn trim(&mut self) {
- if self.last == Last::Space {
- self.par.pop();
- self.last = Last::Other;
+ /// Insert an inline-level element into the current paragraph.
+ fn push_inline(&mut self, child: ParChild) {
+ if let Some(child) = self.par_last.any() {
+ self.push_inline_impl(child);
+ }
+
+ // The node must be both compatible with the current page and the
+ // current paragraph.
+ self.make_flow_compatible(child.styles());
+ self.make_par_compatible(child.styles());
+ self.push_inline_impl(child);
+ self.par_last = Last::Any;
+ }
+
+ /// Push a paragraph child, coalescing text nodes with compatible styles.
+ fn push_inline_impl(&mut self, child: ParChild) {
+ if let ParChild::Text(right) = &child {
+ if let Some(ParChild::Text(left)) = self.par.last_mut() {
+ if left.styles.compatible(&right.styles, TextNode::has_property) {
+ left.text.push_str(&right.text);
+ return;
+ }
+ }
+ }
+
+ self.par.push(child);
+ }
+
+ /// Insert a block-level element into the current flow.
+ fn push_block(&mut self, node: PackedNode) {
+ let mut is_placed = false;
+ if let Some(placed) = node.downcast::<PlacedNode>() {
+ is_placed = true;
+
+ // This prevents paragraph spacing after the placed node if it
+ // is completely out-of-flow.
+ if placed.out_of_flow() {
+ self.flow_last = Last::None;
+ }
+ }
+
+ self.parbreak(None);
+ self.make_flow_compatible(&node.styles);
+
+ if let Some(child) = self.flow_last.any() {
+ self.flow.push(child);
+ }
+
+ self.flow.push(FlowChild::Node(node));
+ self.parbreak(None);
+
+ // This prevents paragraph spacing between the placed node and
+ // the paragraph below it.
+ if is_placed {
+ self.flow_last = Last::None;
}
}
/// Advance to the next paragraph.
- fn parbreak(&mut self) {
- self.trim();
+ fn parbreak(&mut self, break_styles: Option<Styles>) {
+ self.finish_par();
- let children = mem::take(&mut self.par);
+ // Insert paragraph spacing.
+ self.flow_last
+ .soft(FlowChild::Parbreak(break_styles.unwrap_or_default()));
+ }
+
+ fn finish_par(&mut self) {
+ let mut children = mem::take(&mut self.par);
let styles = mem::take(&mut self.par_styles);
+ self.par_last = Last::None;
+
+ // No empty paragraphs.
if !children.is_empty() {
+ // Erase any styles that will be inherited anyway.
+ for child in &mut children {
+ child.styles_mut().erase(&styles);
+ }
+
+ if let Some(child) = self.flow_last.any() {
+ self.flow.push(child);
+ }
+
// 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;
}
/// Advance to the next page.
fn pagebreak(&mut self, keep: bool) {
- self.parbreak();
- let children = mem::take(&mut self.flow);
- 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);
+ if self.block {
+ return;
}
- }
-
- /// 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);
- }
- /// 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 => {}
- }
+ self.finish_par();
- // 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;
- }
+ let styles = mem::take(&mut self.flow_styles);
+ let mut children = mem::take(&mut self.flow);
+ self.flow_last = Last::None;
- /// Insert a block-level element into the current flow.
- fn push_block(&mut self, mut child: FlowChild, styles: Styles) {
- self.parbreak();
+ if keep || !children.is_empty() {
+ // Erase any styles that will be inherited anyway.
+ for child in &mut children {
+ child.styles_mut().erase(&styles);
+ }
- match &mut child {
- FlowChild::Spacing(_) => {}
- FlowChild::Node(node) => node.styles.apply(&styles),
+ let node = PageNode { node: FlowNode(children).pack(), styles };
+ self.pages.push(node);
}
-
- // 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
@@ -321,8 +342,8 @@ impl NodePacker {
return;
}
- if !self.par_styles.compatible(&styles, ParNode::has_property) {
- self.parbreak();
+ if !self.is_par_compatible(styles) {
+ self.parbreak(None);
self.par_styles = styles.clone();
return;
}
@@ -331,19 +352,55 @@ impl NodePacker {
}
/// 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) {
+ /// incompatible with the current flow.
+ fn make_flow_compatible(&mut self, styles: &Styles) {
if self.flow.is_empty() && self.par.is_empty() {
- self.page_styles = styles.clone();
+ self.flow_styles = styles.clone();
return;
}
- if !self.page_styles.compatible(&styles, PageNode::has_property) {
+ if !self.is_flow_compatible(styles) {
self.pagebreak(false);
- self.page_styles = styles.clone();
+ self.flow_styles = styles.clone();
return;
}
- self.page_styles.intersect(styles);
+ self.flow_styles.intersect(styles);
+ }
+
+ /// Whether the given styles are compatible with the current page.
+ fn is_par_compatible(&self, styles: &Styles) -> bool {
+ self.par_styles.compatible(&styles, ParNode::has_property)
+ }
+
+ /// Whether the given styles are compatible with the current flow.
+ fn is_flow_compatible(&self, styles: &Styles) -> bool {
+ self.block || self.flow_styles.compatible(&styles, PageNode::has_property)
+ }
+}
+
+/// Finite state machine for spacing coalescing.
+enum Last<N> {
+ None,
+ Any,
+ Soft(N),
+}
+
+impl<N> Last<N> {
+ fn any(&mut self) -> Option<N> {
+ match mem::replace(self, Self::Any) {
+ Self::Soft(soft) => Some(soft),
+ _ => None,
+ }
+ }
+
+ fn soft(&mut self, soft: N) {
+ if let Self::Any = self {
+ *self = Self::Soft(soft);
+ }
+ }
+
+ fn hard(&mut self) {
+ *self = Self::None;
}
}
diff --git a/src/eval/styles.rs b/src/eval/styles.rs
index 9d204843..555c2a61 100644
--- a/src/eval/styles.rs
+++ b/src/eval/styles.rs
@@ -11,7 +11,7 @@ use std::rc::Rc;
/// A map of style properties.
#[derive(Default, Clone, Hash)]
pub struct Styles {
- pub(crate) map: Vec<(StyleId, Entry)>,
+ map: Vec<(StyleId, Entry)>,
}
impl Styles {
@@ -21,10 +21,7 @@ impl Styles {
}
/// 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,
- {
+ pub fn one<P: Property>(key: P, value: P::Value) -> Self {
let mut styles = Self::new();
styles.set(key, value);
styles
@@ -36,21 +33,31 @@ impl Styles {
}
/// Set the value for a style property.
- pub fn set<P: Property>(&mut self, key: P, value: P::Value)
- where
- P::Value: Debug + Hash + PartialEq + 'static,
- {
+ pub fn set<P: Property>(&mut self, key: P, value: P::Value) {
let id = StyleId::of::<P>();
- let entry = Entry::new(key, value);
-
for pair in &mut self.map {
if pair.0 == id {
- pair.1 = entry;
+ let prev = pair.1.downcast::<P::Value>().unwrap();
+ let folded = P::combine(value, prev.clone());
+ pair.1 = Entry::new(key, folded);
+ return;
+ }
+ }
+
+ self.map.push((id, Entry::new(key, value)));
+ }
+
+ /// Toggle a boolean style property.
+ pub fn toggle<P: Property<Value = bool>>(&mut self, key: P) {
+ let id = StyleId::of::<P>();
+ for (i, pair) in self.map.iter_mut().enumerate() {
+ if pair.0 == id {
+ self.map.swap_remove(i);
return;
}
}
- self.map.push((id, entry));
+ self.map.push((id, Entry::new(key, true)));
}
/// Get the value of a copyable style property.
@@ -84,10 +91,15 @@ impl Styles {
///
/// 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());
+ 'outer: for pair in &outer.map {
+ for (id, entry) in &mut self.map {
+ if pair.0 == *id {
+ entry.apply(&pair.1);
+ continue 'outer;
+ }
}
+
+ self.map.push(pair.clone());
}
}
@@ -105,12 +117,18 @@ impl Styles {
self.map.retain(|a| other.map.iter().any(|b| a == b));
}
+ /// Keep only those styles that are not also in `other`.
+ pub fn erase(&mut self, other: &Self) {
+ self.map.retain(|a| other.map.iter().all(|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,
{
+ // TODO(set): Filtered length + one direction equal should suffice.
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))
@@ -119,73 +137,88 @@ impl Styles {
impl Debug for Styles {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.write_str("Styles ")?;
- f.debug_set().entries(self.map.iter().map(|pair| &pair.1)).finish()
+ if f.alternate() {
+ for pair in &self.map {
+ writeln!(f, "{:#?}", pair.1)?;
+ }
+ Ok(())
+ } else {
+ f.write_str("Styles ")?;
+ f.debug_set().entries(self.map.iter().map(|pair| &pair.1)).finish()
+ }
+ }
+}
+
+impl PartialEq for Styles {
+ fn eq(&self, other: &Self) -> bool {
+ self.compatible(other, |_| true)
}
}
/// An entry for a single style property.
#[derive(Clone)]
-pub(crate) struct Entry {
- #[cfg(debug_assertions)]
- name: &'static str,
- value: Rc<dyn Bounds>,
-}
+pub(crate) struct Entry(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 new<P: Property>(key: P, value: P::Value) -> Self {
+ Self(Rc::new((key, value)))
}
fn downcast<T: 'static>(&self) -> Option<&T> {
- self.value.as_any().downcast_ref()
+ self.0.as_any().downcast_ref()
+ }
+
+ fn apply(&mut self, outer: &Self) {
+ *self = self.0.combine(outer);
}
}
impl Debug for Entry {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- #[cfg(debug_assertions)]
- write!(f, "{}: ", self.name)?;
- write!(f, "{:?}", &self.value)
+ self.0.fmt(f)
}
}
impl PartialEq for Entry {
fn eq(&self, other: &Self) -> bool {
- self.value.dyn_eq(other)
+ self.0.dyn_eq(other)
}
}
impl Hash for Entry {
fn hash<H: Hasher>(&self, state: &mut H) {
- state.write_u64(self.value.hash64());
+ state.write_u64(self.0.hash64());
}
}
-trait Bounds: Debug + 'static {
+trait Bounds: 'static {
fn as_any(&self) -> &dyn Any;
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result;
fn dyn_eq(&self, other: &Entry) -> bool;
fn hash64(&self) -> u64;
+ fn combine(&self, outer: &Entry) -> Entry;
}
-impl<T> Bounds for T
+impl<P> Bounds for (P, P::Value)
where
- T: Debug + Hash + PartialEq + 'static,
+ P: Property,
+ P::Value: Debug + Hash + PartialEq + 'static,
{
fn as_any(&self) -> &dyn Any {
- self
+ &self.1
+ }
+
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ if f.alternate() {
+ write!(f, "#[{} = {:?}]", P::NAME, self.1)
+ } else {
+ write!(f, "{}: {:?}", P::NAME, self.1)
+ }
}
fn dyn_eq(&self, other: &Entry) -> bool {
- if let Some(other) = other.downcast::<Self>() {
- self == other
+ if let Some(other) = other.downcast::<P::Value>() {
+ &self.1 == other
} else {
false
}
@@ -194,7 +227,13 @@ where
fn hash64(&self) -> u64 {
// No need to hash the TypeId since there's only one
// valid value type per property.
- fxhash::hash64(self)
+ fxhash::hash64(&self.1)
+ }
+
+ fn combine(&self, outer: &Entry) -> Entry {
+ let outer = outer.downcast::<P::Value>().unwrap();
+ let combined = P::combine(self.1.clone(), outer.clone());
+ Entry::new(self.0, combined)
}
}
@@ -202,14 +241,17 @@ where
///
/// This trait is not intended to be implemented manually, but rather through
/// the `properties!` macro.
-pub trait Property: 'static {
+pub trait Property: Copy + 'static {
/// The type of this property, for example, this could be
/// [`Length`](crate::geom::Length) for a `WIDTH` property.
- type Value;
+ type Value: Debug + Clone + Hash + PartialEq + 'static;
/// The name of the property, used for debug printing.
const NAME: &'static str;
+ /// Combine the property with an outer value.
+ fn combine(inner: Self::Value, outer: Self::Value) -> Self::Value;
+
/// The default value of the property.
fn default() -> Self::Value;
@@ -235,7 +277,8 @@ impl StyleId {
/// Generate the property keys for a node.
macro_rules! properties {
($node:ty, $(
- $(#[$attr:meta])*
+ $(#[doc = $doc:expr])*
+ $(#[fold($combine:expr)])?
$name:ident: $type:ty = $default:expr
),* $(,)?) => {
// TODO(set): Fix possible name clash.
@@ -250,6 +293,13 @@ macro_rules! properties {
pub struct Key<T>(pub PhantomData<T>);
+ impl<T> Copy for Key<T> {}
+ impl<T> Clone for Key<T> {
+ fn clone(&self) -> Self {
+ *self
+ }
+ }
+
impl Property for Key<$type> {
type Value = $type;
@@ -257,6 +307,15 @@ macro_rules! properties {
stringify!($node), "::", stringify!($name)
);
+ #[allow(unused_mut, unused_variables)]
+ fn combine(mut inner: Self::Value, outer: Self::Value) -> Self::Value {
+ $(
+ let combine: fn(Self::Value, Self::Value) -> Self::Value = $combine;
+ inner = combine(inner, outer);
+ )?
+ inner
+ }
+
fn default() -> Self::Value {
$default
}
@@ -275,7 +334,7 @@ macro_rules! properties {
false || $(id == StyleId::of::<$name::Key<$type>>())||*
}
- $($(#[$attr])* pub const $name: $name::Key<$type>
+ $($(#[doc = $doc])* pub const $name: $name::Key<$type>
= $name::Key(PhantomData);)*
}
}
@@ -284,9 +343,9 @@ macro_rules! properties {
/// Set a style property to a value if the value is `Some`.
macro_rules! set {
- ($ctx:expr, $target:expr => $value:expr) => {
+ ($styles:expr, $target:expr => $value:expr) => {
if let Some(v) = $value {
- $ctx.styles.set($target, v);
+ $styles.set($target, v);
}
};
}
diff --git a/src/eval/value.rs b/src/eval/value.rs
index c2a284eb..2cf82a26 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -26,7 +26,7 @@ pub enum Value {
Float(f64),
/// A length: `12pt`, `3cm`.
Length(Length),
- /// An angle: `1.5rad`, `90deg`.
+ /// An angle: `1.5rad`, `90deg`.
Angle(Angle),
/// A relative value: `50%`.
Relative(Relative),
@@ -146,7 +146,7 @@ impl Debug for Value {
Self::Str(v) => Debug::fmt(v, f),
Self::Array(v) => Debug::fmt(v, f),
Self::Dict(v) => Debug::fmt(v, f),
- Self::Node(v) => Debug::fmt(v, f),
+ Self::Node(_) => f.pad("<template>"),
Self::Func(v) => Debug::fmt(v, f),
Self::Dyn(v) => Debug::fmt(v, f),
}
@@ -386,13 +386,13 @@ primitive! { f64: "float", Float, Int(v) => v as f64 }
primitive! { Length: "length", Length }
primitive! { Angle: "angle", Angle }
primitive! { Relative: "relative", Relative }
-primitive! { Linear: "linear", Linear, Length(v) => v.into(), Relative(v) => v.into() }
-primitive! { Fractional: "fractional", Fractional }
+primitive! { Linear: "relative length", Linear, Length(v) => v.into(), Relative(v) => v.into() }
+primitive! { Fractional: "fractional length", Fractional }
primitive! { Color: "color", Color }
primitive! { EcoString: "string", Str }
primitive! { Array: "array", Array }
primitive! { Dict: "dictionary", Dict }
-primitive! { Node: "node", Node }
+primitive! { Node: "template", Node }
primitive! { Function: "function", Func }
impl Cast<Value> for Value {