summaryrefslogtreecommitdiff
path: root/src/model
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-11-02 14:48:51 +0100
committerLaurenz <laurmaedje@gmail.com>2022-11-02 14:48:51 +0100
commit56342bd972a13ffe21beaf2b87ab7eb1597704b4 (patch)
tree78f9549141e753dde4a938670c54f3fe8695a058 /src/model
parent37ac5d966ebaf97ac79c507028cd5b742b510b89 (diff)
Move layout traits into library
Diffstat (limited to 'src/model')
-rw-r--r--src/model/capture.rs186
-rw-r--r--src/model/collapse.rs116
-rw-r--r--src/model/content.rs70
-rw-r--r--src/model/fold.rs81
-rw-r--r--src/model/func.rs187
-rw-r--r--src/model/layout.rs142
-rw-r--r--src/model/mod.rs33
-rw-r--r--src/model/ops.rs3
-rw-r--r--src/model/property.rs195
-rw-r--r--src/model/raw.rs149
-rw-r--r--src/model/realize.rs486
-rw-r--r--src/model/recipe.rs185
-rw-r--r--src/model/resolve.rs84
-rw-r--r--src/model/str.rs3
-rw-r--r--src/model/styles.rs525
-rw-r--r--src/model/value.rs2
16 files changed, 734 insertions, 1713 deletions
diff --git a/src/model/capture.rs b/src/model/capture.rs
deleted file mode 100644
index c4c107b2..00000000
--- a/src/model/capture.rs
+++ /dev/null
@@ -1,186 +0,0 @@
-use super::{Scope, Scopes, Value};
-use crate::syntax::ast::TypedNode;
-use crate::syntax::{ast, SyntaxNode};
-
-/// A visitor that captures variable slots.
-pub struct CapturesVisitor<'a> {
- external: &'a Scopes<'a>,
- internal: Scopes<'a>,
- captures: Scope,
-}
-
-impl<'a> CapturesVisitor<'a> {
- /// Create a new visitor for the given external scopes.
- pub fn new(external: &'a Scopes) -> Self {
- Self {
- external,
- internal: Scopes::new(None),
- captures: Scope::new(),
- }
- }
-
- /// Return the scope of captured variables.
- pub fn finish(self) -> Scope {
- self.captures
- }
-
- /// Bind a new internal variable.
- pub fn bind(&mut self, ident: ast::Ident) {
- self.internal.top.define(ident.take(), Value::None);
- }
-
- /// Capture a variable if it isn't internal.
- pub fn capture(&mut self, ident: ast::Ident) {
- if self.internal.get(&ident).is_err() {
- if let Ok(value) = self.external.get(&ident) {
- self.captures.define_captured(ident.take(), value.clone());
- }
- }
- }
-
- /// Visit any node and collect all captured variables.
- pub fn visit(&mut self, node: &SyntaxNode) {
- match node.cast() {
- // Every identifier is a potential variable that we need to capture.
- // Identifiers that shouldn't count as captures because they
- // actually bind a new name are handled below (individually through
- // the expressions that contain them).
- Some(ast::Expr::Ident(ident)) => self.capture(ident),
-
- // Code and content blocks create a scope.
- Some(ast::Expr::Code(_) | ast::Expr::Content(_)) => {
- self.internal.enter();
- for child in node.children() {
- self.visit(child);
- }
- self.internal.exit();
- }
-
- // A closure contains parameter bindings, which are bound before the
- // body is evaluated. Care must be taken so that the default values
- // of named parameters cannot access previous parameter bindings.
- Some(ast::Expr::Closure(expr)) => {
- for param in expr.params() {
- if let ast::Param::Named(named) = param {
- self.visit(named.expr().as_untyped());
- }
- }
-
- for param in expr.params() {
- match param {
- ast::Param::Pos(ident) => self.bind(ident),
- ast::Param::Named(named) => self.bind(named.name()),
- ast::Param::Sink(ident) => self.bind(ident),
- }
- }
-
- self.visit(expr.body().as_untyped());
- }
-
- // A let expression contains a binding, but that binding is only
- // active after the body is evaluated.
- Some(ast::Expr::Let(expr)) => {
- if let Some(init) = expr.init() {
- self.visit(init.as_untyped());
- }
- self.bind(expr.binding());
- }
-
- // A show rule contains a binding, but that binding is only active
- // after the target has been evaluated.
- Some(ast::Expr::Show(show)) => {
- self.visit(show.pattern().as_untyped());
- if let Some(binding) = show.binding() {
- self.bind(binding);
- }
- self.visit(show.body().as_untyped());
- }
-
- // A for loop contains one or two bindings in its pattern. These are
- // active after the iterable is evaluated but before the body is
- // evaluated.
- Some(ast::Expr::For(expr)) => {
- self.visit(expr.iter().as_untyped());
- let pattern = expr.pattern();
- if let Some(key) = pattern.key() {
- self.bind(key);
- }
- self.bind(pattern.value());
- self.visit(expr.body().as_untyped());
- }
-
- // An import contains items, but these are active only after the
- // path is evaluated.
- Some(ast::Expr::Import(expr)) => {
- self.visit(expr.path().as_untyped());
- if let ast::Imports::Items(items) = expr.imports() {
- for item in items {
- self.bind(item);
- }
- }
- }
-
- // Everything else is traversed from left to right.
- _ => {
- for child in node.children() {
- self.visit(child);
- }
- }
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::syntax::parse;
-
- #[track_caller]
- fn test(text: &str, result: &[&str]) {
- let mut scopes = Scopes::new(None);
- scopes.top.define("x", 0);
- scopes.top.define("y", 0);
- scopes.top.define("z", 0);
-
- let mut visitor = CapturesVisitor::new(&scopes);
- let root = parse(text);
- visitor.visit(&root);
-
- let captures = visitor.finish();
- let mut names: Vec<_> = captures.iter().map(|(k, _)| k).collect();
- names.sort();
-
- assert_eq!(names, result);
- }
-
- #[test]
- fn test_captures() {
- // Let binding and function definition.
- test("#let x = x", &["x"]);
- test("#let x; {x + y}", &["y"]);
- test("#let f(x, y) = x + y", &[]);
-
- // Closure with different kinds of params.
- test("{(x, y) => x + z}", &["z"]);
- test("{(x: y, z) => x + z}", &["y"]);
- test("{(..x) => x + y}", &["y"]);
- test("{(x, y: x + z) => x + y}", &["x", "z"]);
-
- // Show rule.
- test("#show x: y as x", &["y"]);
- test("#show x: y as x + z", &["y", "z"]);
- test("#show x: x as x", &["x"]);
-
- // For loop.
- test("#for x in y { x + z }", &["y", "z"]);
- test("#for x, y in y { x + y }", &["y"]);
-
- // Import.
- test("#import x, y from z", &["z"]);
- test("#import x, y, z from x + y", &["x", "y"]);
-
- // Blocks.
- test("{ let x = 1; { let y = 2; y }; x + y }", &["y"]);
- test("[#let x = 1]#x", &["x"]);
- }
-}
diff --git a/src/model/collapse.rs b/src/model/collapse.rs
deleted file mode 100644
index f57a3a42..00000000
--- a/src/model/collapse.rs
+++ /dev/null
@@ -1,116 +0,0 @@
-use super::{StyleChain, StyleVec, StyleVecBuilder};
-
-/// A wrapper around a [`StyleVecBuilder`] that allows to collapse items.
-pub(super) struct CollapsingBuilder<'a, T> {
- /// The internal builder.
- builder: StyleVecBuilder<'a, T>,
- /// Staged weak and ignorant items that we can't yet commit to the builder.
- /// The option is `Some(_)` for weak items and `None` for ignorant items.
- staged: Vec<(T, StyleChain<'a>, Option<u8>)>,
- /// What the last non-ignorant item was.
- last: Last,
-}
-
-/// What the last non-ignorant item was.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-enum Last {
- Weak,
- Destructive,
- Supportive,
-}
-
-impl<'a, T> CollapsingBuilder<'a, T> {
- /// Create a new style-vec builder.
- pub fn new() -> Self {
- Self {
- builder: StyleVecBuilder::new(),
- staged: vec![],
- last: Last::Destructive,
- }
- }
-
- /// Whether the builder is empty.
- pub fn is_empty(&self) -> bool {
- self.builder.is_empty() && self.staged.is_empty()
- }
-
- /// Can only exist when there is at least one supportive item to its left
- /// and to its right, with no destructive items in between. There may be
- /// ignorant items in between in both directions.
- ///
- /// Between weak items, there may be at least one per layer and among the
- /// candidates the strongest one (smallest `weakness`) wins. When tied,
- /// the one that compares larger through `PartialOrd` wins.
- pub fn weak(&mut self, item: T, styles: StyleChain<'a>, weakness: u8)
- where
- T: PartialOrd,
- {
- if self.last == Last::Destructive {
- return;
- }
-
- if self.last == Last::Weak {
- if let Some(i) =
- self.staged.iter().position(|(prev_item, _, prev_weakness)| {
- prev_weakness.map_or(false, |prev_weakness| {
- weakness < prev_weakness
- || (weakness == prev_weakness && item > *prev_item)
- })
- })
- {
- self.staged.remove(i);
- } else {
- return;
- }
- }
-
- self.staged.push((item, styles, Some(weakness)));
- self.last = Last::Weak;
- }
-
- /// Forces nearby weak items to collapse.
- pub fn destructive(&mut self, item: T, styles: StyleChain<'a>) {
- self.flush(false);
- self.builder.push(item, styles);
- self.last = Last::Destructive;
- }
-
- /// Allows nearby weak items to exist.
- pub fn supportive(&mut self, item: T, styles: StyleChain<'a>) {
- self.flush(true);
- self.builder.push(item, styles);
- self.last = Last::Supportive;
- }
-
- /// Has no influence on other items.
- pub fn ignorant(&mut self, item: T, styles: StyleChain<'a>) {
- self.staged.push((item, styles, None));
- }
-
- /// Iterate over the contained items.
- pub fn items(&self) -> impl DoubleEndedIterator<Item = &T> {
- self.builder.items().chain(self.staged.iter().map(|(item, ..)| item))
- }
-
- /// Return the finish style vec and the common prefix chain.
- pub fn finish(mut self) -> (StyleVec<T>, StyleChain<'a>) {
- self.flush(false);
- self.builder.finish()
- }
-
- /// Push the staged items, filtering out weak items if `supportive` is
- /// false.
- fn flush(&mut self, supportive: bool) {
- for (item, styles, meta) in self.staged.drain(..) {
- if supportive || meta.is_none() {
- self.builder.push(item, styles);
- }
- }
- }
-}
-
-impl<'a, T> Default for CollapsingBuilder<'a, T> {
- fn default() -> Self {
- Self::new()
- }
-}
diff --git a/src/model/content.rs b/src/model/content.rs
index 170d47a1..1cffa773 100644
--- a/src/model/content.rs
+++ b/src/model/content.rs
@@ -5,18 +5,12 @@ use std::iter::{self, Sum};
use std::ops::{Add, AddAssign};
use std::sync::Arc;
-use comemo::Tracked;
use siphasher::sip128::{Hasher128, SipHasher};
use typst_macros::node;
-use super::{
- Args, Barrier, Builder, Key, Layout, Level, Property, Regions, Scratch, Selector,
- StyleChain, StyleEntry, StyleMap, Vm,
-};
+use super::{Args, Key, Property, Selector, StyleEntry, StyleMap, Vm};
use crate::diag::{SourceResult, StrResult};
-use crate::frame::Frame;
use crate::util::ReadableTypeId;
-use crate::World;
/// Composable representation of styled content.
///
@@ -40,22 +34,27 @@ impl Content {
}
}
+ /// Whether the content is empty.
pub fn is_empty(&self) -> bool {
self.downcast::<SequenceNode>().map_or(false, |seq| seq.0.is_empty())
}
+ /// The id of the contained node.
pub fn id(&self) -> NodeId {
(*self.0).id()
}
+ /// Whether the contained node is of type `T`.
pub fn is<T: 'static>(&self) -> bool {
(*self.0).as_any().is::<T>()
}
+ /// Cast to `T` if the contained node is of type `T`.
pub fn downcast<T: 'static>(&self) -> Option<&T> {
(*self.0).as_any().downcast_ref::<T>()
}
+ /// Try to cast to a mutable instance of `T`.
fn try_downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
Arc::get_mut(&mut self.0)?.as_any_mut().downcast_mut::<T>()
}
@@ -120,51 +119,6 @@ impl Content {
pub fn unguard(&self, sel: Selector) -> Self {
self.clone().styled_with_entry(StyleEntry::Unguard(sel))
}
-
- #[comemo::memoize]
- pub fn layout_block(
- &self,
- world: Tracked<dyn World>,
- regions: &Regions,
- styles: StyleChain,
- ) -> SourceResult<Vec<Frame>> {
- let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
- let styles = barrier.chain(&styles);
-
- if let Some(node) = self.to::<dyn Layout>() {
- if node.level() == Level::Block {
- return node.layout(world, regions, styles);
- }
- }
-
- let scratch = Scratch::default();
- let mut builder = Builder::new(world, &scratch, false);
- builder.accept(self, styles)?;
- let (flow, shared) = builder.into_flow(styles)?;
- flow.layout(world, regions, shared)
- }
-
-
- #[comemo::memoize]
- pub fn layout_inline(
- &self,
- world: Tracked<dyn World>,
- regions: &Regions,
- styles: StyleChain,
- ) -> SourceResult<Vec<Frame>> {
- let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
- let styles = barrier.chain(&styles);
-
- if let Some(node) = self.to::<dyn Layout>() {
- return node.layout(world, regions, styles);
- }
-
- let scratch = Scratch::default();
- let mut builder = Builder::new(world, &scratch, false);
- builder.accept(self, styles)?;
- let (flow, shared) = builder.into_flow(styles)?;
- flow.layout(world, regions, shared)
- }
}
impl Default for Content {
@@ -293,6 +247,11 @@ pub trait Node: 'static {
fn vtable(&self, id: TypeId) -> Option<*const ()>;
}
+/// A capability a node can have.
+///
+/// This is implemented by trait objects.
+pub trait Capability: 'static + Send + Sync {}
+
/// A unique identifier for a node.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct NodeId(ReadableTypeId);
@@ -310,15 +269,12 @@ impl Debug for NodeId {
}
}
-/// A capability a node can have.
-///
-/// This is implemented by trait objects.
-pub trait Capability: 'static + Send + Sync {}
-
/// A node with applied styles.
#[derive(Clone, Hash)]
pub struct StyledNode {
+ /// The styled content.
pub sub: Content,
+ /// The styles.
pub map: StyleMap,
}
diff --git a/src/model/fold.rs b/src/model/fold.rs
deleted file mode 100644
index bc85be01..00000000
--- a/src/model/fold.rs
+++ /dev/null
@@ -1,81 +0,0 @@
-use super::Smart;
-use crate::geom::{Abs, Corners, Length, Rel, Sides};
-
-/// A property that is folded to determine its final value.
-pub trait Fold {
- /// The type of the folded output.
- type Output;
-
- /// Fold this inner value with an outer folded value.
- fn fold(self, outer: Self::Output) -> Self::Output;
-}
-
-impl<T> Fold for Option<T>
-where
- T: Fold,
- T::Output: Default,
-{
- type Output = Option<T::Output>;
-
- fn fold(self, outer: Self::Output) -> Self::Output {
- self.map(|inner| inner.fold(outer.unwrap_or_default()))
- }
-}
-
-impl<T> Fold for Smart<T>
-where
- T: Fold,
- T::Output: Default,
-{
- type Output = Smart<T::Output>;
-
- fn fold(self, outer: Self::Output) -> Self::Output {
- self.map(|inner| inner.fold(outer.unwrap_or_default()))
- }
-}
-
-impl<T> Fold for Sides<T>
-where
- T: Fold,
-{
- type Output = Sides<T::Output>;
-
- fn fold(self, outer: Self::Output) -> Self::Output {
- self.zip(outer, |inner, outer| inner.fold(outer))
- }
-}
-
-impl Fold for Sides<Option<Rel<Abs>>> {
- type Output = Sides<Rel<Abs>>;
-
- fn fold(self, outer: Self::Output) -> Self::Output {
- self.zip(outer, |inner, outer| inner.unwrap_or(outer))
- }
-}
-
-impl Fold for Sides<Option<Smart<Rel<Length>>>> {
- type Output = Sides<Smart<Rel<Length>>>;
-
- fn fold(self, outer: Self::Output) -> Self::Output {
- self.zip(outer, |inner, outer| inner.unwrap_or(outer))
- }
-}
-
-impl<T> Fold for Corners<T>
-where
- T: Fold,
-{
- type Output = Corners<T::Output>;
-
- fn fold(self, outer: Self::Output) -> Self::Output {
- self.zip(outer, |inner, outer| inner.fold(outer))
- }
-}
-
-impl Fold for Corners<Option<Rel<Abs>>> {
- type Output = Corners<Rel<Abs>>;
-
- fn fold(self, outer: Self::Output) -> Self::Output {
- self.zip(outer, |inner, outer| inner.unwrap_or(outer))
- }
-}
diff --git a/src/model/func.rs b/src/model/func.rs
index 47ad4436..dff58233 100644
--- a/src/model/func.rs
+++ b/src/model/func.rs
@@ -6,8 +6,8 @@ use comemo::{Track, Tracked};
use super::{Args, Eval, Flow, Node, NodeId, Route, Scope, Scopes, StyleMap, Value, Vm};
use crate::diag::{SourceResult, StrResult};
-use crate::syntax::ast::Expr;
-use crate::syntax::SourceId;
+use crate::syntax::ast::{self, Expr, TypedNode};
+use crate::syntax::{SourceId, SyntaxNode};
use crate::util::EcoString;
use crate::World;
@@ -227,3 +227,186 @@ impl Closure {
result
}
}
+
+/// A visitor that determines which variables to capture for a closure.
+pub struct CapturesVisitor<'a> {
+ external: &'a Scopes<'a>,
+ internal: Scopes<'a>,
+ captures: Scope,
+}
+
+impl<'a> CapturesVisitor<'a> {
+ /// Create a new visitor for the given external scopes.
+ pub fn new(external: &'a Scopes) -> Self {
+ Self {
+ external,
+ internal: Scopes::new(None),
+ captures: Scope::new(),
+ }
+ }
+
+ /// Return the scope of captured variables.
+ pub fn finish(self) -> Scope {
+ self.captures
+ }
+
+ /// Bind a new internal variable.
+ pub fn bind(&mut self, ident: ast::Ident) {
+ self.internal.top.define(ident.take(), Value::None);
+ }
+
+ /// Capture a variable if it isn't internal.
+ pub fn capture(&mut self, ident: ast::Ident) {
+ if self.internal.get(&ident).is_err() {
+ if let Ok(value) = self.external.get(&ident) {
+ self.captures.define_captured(ident.take(), value.clone());
+ }
+ }
+ }
+
+ /// Visit any node and collect all captured variables.
+ pub fn visit(&mut self, node: &SyntaxNode) {
+ match node.cast() {
+ // Every identifier is a potential variable that we need to capture.
+ // Identifiers that shouldn't count as captures because they
+ // actually bind a new name are handled below (individually through
+ // the expressions that contain them).
+ Some(ast::Expr::Ident(ident)) => self.capture(ident),
+
+ // Code and content blocks create a scope.
+ Some(ast::Expr::Code(_) | ast::Expr::Content(_)) => {
+ self.internal.enter();
+ for child in node.children() {
+ self.visit(child);
+ }
+ self.internal.exit();
+ }
+
+ // A closure contains parameter bindings, which are bound before the
+ // body is evaluated. Care must be taken so that the default values
+ // of named parameters cannot access previous parameter bindings.
+ Some(ast::Expr::Closure(expr)) => {
+ for param in expr.params() {
+ if let ast::Param::Named(named) = param {
+ self.visit(named.expr().as_untyped());
+ }
+ }
+
+ for param in expr.params() {
+ match param {
+ ast::Param::Pos(ident) => self.bind(ident),
+ ast::Param::Named(named) => self.bind(named.name()),
+ ast::Param::Sink(ident) => self.bind(ident),
+ }
+ }
+
+ self.visit(expr.body().as_untyped());
+ }
+
+ // A let expression contains a binding, but that binding is only
+ // active after the body is evaluated.
+ Some(ast::Expr::Let(expr)) => {
+ if let Some(init) = expr.init() {
+ self.visit(init.as_untyped());
+ }
+ self.bind(expr.binding());
+ }
+
+ // A show rule contains a binding, but that binding is only active
+ // after the target has been evaluated.
+ Some(ast::Expr::Show(show)) => {
+ self.visit(show.pattern().as_untyped());
+ if let Some(binding) = show.binding() {
+ self.bind(binding);
+ }
+ self.visit(show.body().as_untyped());
+ }
+
+ // A for loop contains one or two bindings in its pattern. These are
+ // active after the iterable is evaluated but before the body is
+ // evaluated.
+ Some(ast::Expr::For(expr)) => {
+ self.visit(expr.iter().as_untyped());
+ let pattern = expr.pattern();
+ if let Some(key) = pattern.key() {
+ self.bind(key);
+ }
+ self.bind(pattern.value());
+ self.visit(expr.body().as_untyped());
+ }
+
+ // An import contains items, but these are active only after the
+ // path is evaluated.
+ Some(ast::Expr::Import(expr)) => {
+ self.visit(expr.path().as_untyped());
+ if let ast::Imports::Items(items) = expr.imports() {
+ for item in items {
+ self.bind(item);
+ }
+ }
+ }
+
+ // Everything else is traversed from left to right.
+ _ => {
+ for child in node.children() {
+ self.visit(child);
+ }
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::syntax::parse;
+
+ #[track_caller]
+ fn test(text: &str, result: &[&str]) {
+ let mut scopes = Scopes::new(None);
+ scopes.top.define("x", 0);
+ scopes.top.define("y", 0);
+ scopes.top.define("z", 0);
+
+ let mut visitor = CapturesVisitor::new(&scopes);
+ let root = parse(text);
+ visitor.visit(&root);
+
+ let captures = visitor.finish();
+ let mut names: Vec<_> = captures.iter().map(|(k, _)| k).collect();
+ names.sort();
+
+ assert_eq!(names, result);
+ }
+
+ #[test]
+ fn test_captures() {
+ // Let binding and function definition.
+ test("#let x = x", &["x"]);
+ test("#let x; {x + y}", &["y"]);
+ test("#let f(x, y) = x + y", &[]);
+
+ // Closure with different kinds of params.
+ test("{(x, y) => x + z}", &["z"]);
+ test("{(x: y, z) => x + z}", &["y"]);
+ test("{(..x) => x + y}", &["y"]);
+ test("{(x, y: x + z) => x + y}", &["x", "z"]);
+
+ // Show rule.
+ test("#show x: y as x", &["y"]);
+ test("#show x: y as x + z", &["y", "z"]);
+ test("#show x: x as x", &["x"]);
+
+ // For loop.
+ test("#for x in y { x + z }", &["y", "z"]);
+ test("#for x, y in y { x + y }", &["y"]);
+
+ // Import.
+ test("#import x, y from z", &["z"]);
+ test("#import x, y, z from x + y", &["x", "y"]);
+
+ // Blocks.
+ test("{ let x = 1; { let y = 2; y }; x + y }", &["y"]);
+ test("[#let x = 1]#x", &["x"]);
+ }
+}
diff --git a/src/model/layout.rs b/src/model/layout.rs
deleted file mode 100644
index e8aa0e96..00000000
--- a/src/model/layout.rs
+++ /dev/null
@@ -1,142 +0,0 @@
-//! Layouting infrastructure.
-
-use std::hash::Hash;
-
-use comemo::Tracked;
-
-use super::{Builder, Capability, Content, Scratch, StyleChain};
-use crate::diag::SourceResult;
-use crate::frame::Frame;
-use crate::geom::{Abs, Axes, Size};
-use crate::World;
-
-/// Layout content into a collection of pages.
-#[comemo::memoize]
-pub fn layout(world: Tracked<dyn World>, content: &Content) -> SourceResult<Vec<Frame>> {
- let styles = StyleChain::with_root(&world.config().styles);
- let scratch = Scratch::default();
-
- let mut builder = Builder::new(world, &scratch, true);
- builder.accept(content, styles)?;
-
- let (doc, shared) = builder.into_doc(styles)?;
- doc.layout(world, shared)
-}
-
-/// A node that can be layouted into a sequence of regions.
-///
-/// Layouting returns one frame per used region.
-pub trait Layout: 'static + Sync + Send {
- /// Layout this node into the given regions, producing frames.
- fn layout(
- &self,
- world: Tracked<dyn World>,
- regions: &Regions,
- styles: StyleChain,
- ) -> SourceResult<Vec<Frame>>;
-
- /// Whether this is an inline-level or block-level node.
- fn level(&self) -> Level;
-}
-
-impl Capability for dyn Layout {}
-
-/// At which level a node operates.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub enum Level {
- Inline,
- Block,
-}
-
-/// A sequence of regions to layout into.
-#[derive(Debug, Clone, Hash)]
-pub struct Regions {
- /// The (remaining) size of the first region.
- pub first: Size,
- /// The base size for relative sizing.
- pub base: Size,
- /// The height of followup regions. The width is the same for all regions.
- pub backlog: Vec<Abs>,
- /// The height of the final region that is repeated once the backlog is
- /// drained. The width is the same for all regions.
- pub last: Option<Abs>,
- /// Whether nodes should expand to fill the regions instead of shrinking to
- /// fit the content.
- pub expand: Axes<bool>,
-}
-
-impl Regions {
- /// Create a new region sequence with exactly one region.
- pub fn one(size: Size, base: Size, expand: Axes<bool>) -> Self {
- Self {
- first: size,
- base,
- backlog: vec![],
- last: None,
- expand,
- }
- }
-
- /// Create a new sequence of same-size regions that repeats indefinitely.
- pub fn repeat(size: Size, base: Size, expand: Axes<bool>) -> Self {
- Self {
- first: size,
- base,
- backlog: vec![],
- last: Some(size.y),
- expand,
- }
- }
-
- /// Create new regions where all sizes are mapped with `f`.
- ///
- /// Note that since all regions must have the same width, the width returned
- /// by `f` is ignored for the backlog and the final region.
- pub fn map<F>(&self, mut f: F) -> Self
- where
- F: FnMut(Size) -> Size,
- {
- let x = self.first.x;
- Self {
- first: f(self.first),
- base: f(self.base),
- backlog: self.backlog.iter().map(|&y| f(Size::new(x, y)).y).collect(),
- last: self.last.map(|y| f(Size::new(x, y)).y),
- expand: self.expand,
- }
- }
-
- /// Whether the first region is full and a region break is called for.
- pub fn is_full(&self) -> bool {
- Abs::zero().fits(self.first.y) && !self.in_last()
- }
-
- /// Whether the first region is the last usable region.
- ///
- /// If this is true, calling `next()` will have no effect.
- pub fn in_last(&self) -> bool {
- self.backlog.len() == 0 && self.last.map_or(true, |height| self.first.y == height)
- }
-
- /// Advance to the next region if there is any.
- pub fn next(&mut self) {
- if let Some(height) = (!self.backlog.is_empty())
- .then(|| self.backlog.remove(0))
- .or(self.last)
- {
- self.first.y = height;
- self.base.y = height;
- }
- }
-
- /// An iterator that returns the sizes of the first and all following
- /// regions, equivalently to what would be produced by calling
- /// [`next()`](Self::next) repeatedly until all regions are exhausted.
- /// This iterater may be infinite.
- pub fn iter(&self) -> impl Iterator<Item = Size> + '_ {
- let first = std::iter::once(self.first);
- let backlog = self.backlog.iter();
- let last = self.last.iter().cycle();
- first.chain(backlog.chain(last).map(|&h| Size::new(self.first.x, h)))
- }
-}
diff --git a/src/model/mod.rs b/src/model/mod.rs
index aba9514c..b4f8f653 100644
--- a/src/model/mod.rs
+++ b/src/model/mod.rs
@@ -1,14 +1,6 @@
//! Layout and computation model.
#[macro_use]
-mod styles;
-mod collapse;
-mod content;
-mod eval;
-mod layout;
-mod property;
-mod recipe;
-#[macro_use]
mod cast;
#[macro_use]
mod array;
@@ -18,38 +10,29 @@ mod dict;
mod str;
#[macro_use]
mod value;
+#[macro_use]
+mod styles;
mod args;
-mod capture;
-mod fold;
+mod content;
+mod eval;
mod func;
-pub mod methods;
-pub mod ops;
-mod raw;
-mod realize;
-mod resolve;
mod scope;
mod vm;
+pub mod methods;
+pub mod ops;
+
pub use self::str::*;
pub use args::*;
pub use array::*;
-pub use capture::*;
pub use cast::*;
pub use content::*;
pub use dict::*;
pub use eval::*;
-pub use fold::*;
pub use func::*;
-pub use layout::*;
-pub use property::*;
-pub use raw::*;
-pub use recipe::*;
-pub use resolve::*;
pub use scope::*;
pub use styles::*;
-pub use typst_macros::node;
pub use value::*;
pub use vm::*;
-// use collapse::*;
-use realize::*;
+pub use typst_macros::{capability, node};
diff --git a/src/model/ops.rs b/src/model/ops.rs
index 7a8c6950..7eb814c1 100644
--- a/src/model/ops.rs
+++ b/src/model/ops.rs
@@ -2,10 +2,11 @@
use std::cmp::Ordering;
-use super::{Node, RawAlign, RawStroke, Regex, Smart, Value};
+use super::{Node, Regex, Smart, Value};
use crate::diag::StrResult;
use crate::geom::{Axes, Axis, Length, Numeric, Rel};
use crate::library::text::TextNode;
+use crate::library::{RawAlign, RawStroke};
use Value::*;
/// Bail with a type mismatch error.
diff --git a/src/model/property.rs b/src/model/property.rs
deleted file mode 100644
index 3a498d2c..00000000
--- a/src/model/property.rs
+++ /dev/null
@@ -1,195 +0,0 @@
-use std::any::Any;
-use std::fmt::{self, Debug, Formatter};
-use std::hash::Hash;
-use std::sync::Arc;
-
-use comemo::Prehashed;
-
-use super::{Interruption, NodeId, StyleChain};
-use crate::library::layout::PageNode;
-use crate::library::structure::{DescNode, EnumNode, ListNode};
-use crate::library::text::ParNode;
-use crate::util::ReadableTypeId;
-
-/// A style property originating from a set rule or constructor.
-#[derive(Clone, Hash)]
-pub struct Property {
- /// The id of the property's [key](Key).
- key: KeyId,
- /// The id of the node the property belongs to.
- node: NodeId,
- /// Whether the property should only affect the first node down the
- /// hierarchy. Used by constructors.
- scoped: bool,
- /// The property's value.
- value: Arc<Prehashed<dyn Bounds>>,
- /// The name of the property.
- #[cfg(debug_assertions)]
- name: &'static str,
-}
-
-impl Property {
- /// Create a new property from a key-value pair.
- pub fn new<'a, K: Key<'a>>(_: K, value: K::Value) -> Self {
- Self {
- key: KeyId::of::<K>(),
- node: K::node(),
- value: Arc::new(Prehashed::new(value)),
- scoped: false,
- #[cfg(debug_assertions)]
- name: K::NAME,
- }
- }
-
- /// Whether this property has the given key.
- pub fn is<'a, K: Key<'a>>(&self) -> bool {
- self.key == KeyId::of::<K>()
- }
-
- /// Whether this property belongs to the node `T`.
- pub fn is_of<T: 'static>(&self) -> bool {
- self.node == NodeId::of::<T>()
- }
-
- /// Access the property's value if it is of the given key.
- pub fn downcast<'a, K: Key<'a>>(&'a self) -> Option<&'a K::Value> {
- if self.key == KeyId::of::<K>() {
- (**self.value).as_any().downcast_ref()
- } else {
- None
- }
- }
-
- /// The node this property is for.
- pub fn node(&self) -> NodeId {
- self.node
- }
-
- /// Whether the property is scoped.
- pub fn scoped(&self) -> bool {
- self.scoped
- }
-
- /// Make the property scoped.
- pub fn make_scoped(&mut self) {
- self.scoped = true;
- }
-
- /// What kind of structure the property interrupts.
- pub fn interruption(&self) -> Option<Interruption> {
- if self.is_of::<PageNode>() {
- Some(Interruption::Page)
- } else if self.is_of::<ParNode>() {
- Some(Interruption::Par)
- } else if self.is_of::<ListNode>()
- || self.is_of::<EnumNode>()
- || self.is_of::<DescNode>()
- {
- Some(Interruption::List)
- } else {
- None
- }
- }
-}
-
-impl Debug for Property {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- #[cfg(debug_assertions)]
- write!(f, "{} = ", self.name)?;
- write!(f, "{:?}", self.value)?;
- if self.scoped {
- write!(f, " [scoped]")?;
- }
- Ok(())
- }
-}
-
-impl PartialEq for Property {
- fn eq(&self, other: &Self) -> bool {
- self.key == other.key
- && self.value.eq(&other.value)
- && self.scoped == other.scoped
- }
-}
-
-trait Bounds: Debug + Sync + Send + 'static {
- fn as_any(&self) -> &dyn Any;
-}
-
-impl<T> Bounds for T
-where
- T: Debug + Sync + Send + 'static,
-{
- fn as_any(&self) -> &dyn Any {
- self
- }
-}
-
-/// A style property key.
-///
-/// This trait is not intended to be implemented manually, but rather through
-/// the `#[node]` proc-macro.
-pub trait Key<'a>: Copy + 'static {
- /// The unfolded type which this property is stored as in a style map.
- type Value: Debug + Clone + Hash + Sync + Send + 'static;
-
- /// The folded type of value that is returned when reading this property
- /// from a style chain.
- type Output;
-
- /// The name of the property, used for debug printing.
- const NAME: &'static str;
-
- /// The id of the node the key belongs to.
- fn node() -> NodeId;
-
- /// Compute an output value from a sequence of values belonging to this key,
- /// folding if necessary.
- fn get(
- chain: StyleChain<'a>,
- values: impl Iterator<Item = &'a Self::Value>,
- ) -> Self::Output;
-}
-
-/// A unique identifier for a property key.
-#[derive(Copy, Clone, Eq, PartialEq, Hash)]
-struct KeyId(ReadableTypeId);
-
-impl KeyId {
- /// The id of the given key.
- pub fn of<'a, T: Key<'a>>() -> Self {
- Self(ReadableTypeId::of::<T>())
- }
-}
-
-impl Debug for KeyId {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- self.0.fmt(f)
- }
-}
-
-/// A scoped property barrier.
-///
-/// Barriers interact with [scoped](super::StyleMap::scoped) styles: A scoped
-/// style can still be read through a single barrier (the one of the node it
-/// _should_ apply to), but a second barrier will make it invisible.
-#[derive(Copy, Clone, Eq, PartialEq, Hash)]
-pub struct Barrier(NodeId);
-
-impl Barrier {
- /// Create a new barrier for the given node.
- pub fn new(node: NodeId) -> Self {
- Self(node)
- }
-
- /// Whether this barrier is for the node `T`.
- pub fn is_for(&self, node: NodeId) -> bool {
- self.0 == node
- }
-}
-
-impl Debug for Barrier {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(f, "Barrier for {:?}", self.0)
- }
-}
diff --git a/src/model/raw.rs b/src/model/raw.rs
deleted file mode 100644
index b9242b51..00000000
--- a/src/model/raw.rs
+++ /dev/null
@@ -1,149 +0,0 @@
-use std::fmt::{self, Debug, Formatter};
-
-use super::{Fold, Resolve, Smart, StyleChain, Value};
-use crate::geom::{Abs, Align, Axes, Axis, Get, Length, Paint, Stroke};
-use crate::library::text::TextNode;
-
-/// The unresolved alignment representation.
-#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
-pub enum RawAlign {
- /// Align at the start side of the text direction.
- Start,
- /// Align at the end side of the text direction.
- End,
- /// Align at a specific alignment.
- Specific(Align),
-}
-
-impl Resolve for RawAlign {
- type Output = Align;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- let dir = styles.get(TextNode::DIR);
- match self {
- Self::Start => dir.start().into(),
- Self::End => dir.end().into(),
- Self::Specific(align) => align,
- }
- }
-}
-
-impl RawAlign {
- /// The axis this alignment belongs to.
- pub const fn axis(self) -> Axis {
- match self {
- Self::Start | Self::End => Axis::X,
- Self::Specific(align) => align.axis(),
- }
- }
-}
-
-impl From<Align> for RawAlign {
- fn from(align: Align) -> Self {
- Self::Specific(align)
- }
-}
-
-impl Debug for RawAlign {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- match self {
- Self::Start => f.pad("start"),
- Self::End => f.pad("end"),
- Self::Specific(align) => align.fmt(f),
- }
- }
-}
-
-dynamic! {
- RawAlign: "alignment",
-}
-
-dynamic! {
- Axes<RawAlign>: "2d alignment",
-}
-
-castable! {
- Axes<Option<RawAlign>>,
- Expected: "1d or 2d alignment",
- @align: RawAlign => {
- let mut aligns = Axes::default();
- aligns.set(align.axis(), Some(*align));
- aligns
- },
- @aligns: Axes<RawAlign> => aligns.map(Some),
-}
-
-/// The unresolved stroke representation.
-///
-/// In this representation, both fields are optional so that you can pass either
-/// just a paint (`red`), just a thickness (`0.1em`) or both (`2pt + red`) where
-/// this is expected.
-#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct RawStroke<T = Length> {
- /// The stroke's paint.
- pub paint: Smart<Paint>,
- /// The stroke's thickness.
- pub thickness: Smart<T>,
-}
-
-impl RawStroke<Abs> {
- /// Unpack the stroke, filling missing fields from the `default`.
- pub fn unwrap_or(self, default: Stroke) -> Stroke {
- Stroke {
- paint: self.paint.unwrap_or(default.paint),
- thickness: self.thickness.unwrap_or(default.thickness),
- }
- }
-
- /// Unpack the stroke, filling missing fields with the default values.
- pub fn unwrap_or_default(self) -> Stroke {
- self.unwrap_or(Stroke::default())
- }
-}
-
-impl Resolve for RawStroke {
- type Output = RawStroke<Abs>;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- RawStroke {
- paint: self.paint,
- thickness: self.thickness.resolve(styles),
- }
- }
-}
-
-impl Fold for RawStroke<Abs> {
- type Output = Self;
-
- fn fold(self, outer: Self::Output) -> Self::Output {
- Self {
- paint: self.paint.or(outer.paint),
- thickness: self.thickness.or(outer.thickness),
- }
- }
-}
-
-impl<T: Debug> Debug for RawStroke<T> {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- match (self.paint, &self.thickness) {
- (Smart::Custom(paint), Smart::Custom(thickness)) => {
- write!(f, "{thickness:?} + {paint:?}")
- }
- (Smart::Custom(paint), Smart::Auto) => paint.fmt(f),
- (Smart::Auto, Smart::Custom(thickness)) => thickness.fmt(f),
- (Smart::Auto, Smart::Auto) => f.pad("<stroke>"),
- }
- }
-}
-
-dynamic! {
- RawStroke: "stroke",
- Value::Length(thickness) => Self {
- paint: Smart::Auto,
- thickness: Smart::Custom(thickness),
- },
- Value::Color(color) => Self {
- paint: Smart::Custom(color.into()),
- thickness: Smart::Auto,
- },
-}
diff --git a/src/model/realize.rs b/src/model/realize.rs
deleted file mode 100644
index a99abdd3..00000000
--- a/src/model/realize.rs
+++ /dev/null
@@ -1,486 +0,0 @@
-use std::mem;
-
-use comemo::Tracked;
-use typed_arena::Arena;
-
-use super::collapse::CollapsingBuilder;
-use super::{
- Barrier, Content, Interruption, Layout, Level, Node, SequenceNode, Show, StyleChain,
- StyleEntry, StyleMap, StyleVecBuilder, StyledNode, Target,
-};
-use crate::diag::SourceResult;
-use crate::geom::Numeric;
-use crate::library::layout::{
- ColbreakNode, FlowChild, FlowNode, HNode, PageNode, PagebreakNode, PlaceNode, VNode,
-};
-use crate::library::structure::{DocNode, ListItem, ListNode, DESC, ENUM, LIST};
-use crate::library::text::{
- LinebreakNode, ParChild, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode,
-};
-use crate::World;
-
-/// Builds a document or a flow node from content.
-pub(super) struct Builder<'a> {
- /// The core context.
- world: Tracked<'a, dyn World>,
- /// Scratch arenas for building.
- scratch: &'a Scratch<'a>,
- /// The current document building state.
- doc: Option<DocBuilder<'a>>,
- /// The current flow building state.
- flow: FlowBuilder<'a>,
- /// The current paragraph building state.
- par: ParBuilder<'a>,
- /// The current list building state.
- list: ListBuilder<'a>,
-}
-
-/// Temporary storage arenas for building.
-#[derive(Default)]
-pub(super) struct Scratch<'a> {
- /// An arena where intermediate style chains are stored.
- styles: Arena<StyleChain<'a>>,
- /// An arena where intermediate content resulting from show rules is stored.
- templates: Arena<Content>,
-}
-
-impl<'a> Builder<'a> {
- pub fn new(
- world: Tracked<'a, dyn World>,
- scratch: &'a Scratch<'a>,
- top: bool,
- ) -> Self {
- Self {
- world,
- scratch,
- doc: top.then(|| DocBuilder::default()),
- flow: FlowBuilder::default(),
- par: ParBuilder::default(),
- list: ListBuilder::default(),
- }
- }
-
- pub fn into_doc(
- mut self,
- styles: StyleChain<'a>,
- ) -> SourceResult<(DocNode, StyleChain<'a>)> {
- self.interrupt(Interruption::Page, styles, true)?;
- let (pages, shared) = self.doc.unwrap().pages.finish();
- Ok((DocNode(pages), shared))
- }
-
- pub fn into_flow(
- mut self,
- styles: StyleChain<'a>,
- ) -> SourceResult<(FlowNode, StyleChain<'a>)> {
- self.interrupt(Interruption::Par, styles, false)?;
- let (children, shared) = self.flow.0.finish();
- Ok((FlowNode(children), shared))
- }
-
- pub fn accept(
- &mut self,
- content: &'a Content,
- styles: StyleChain<'a>,
- ) -> SourceResult<()> {
- if let Some(node) = content.downcast::<TextNode>() {
- if let Some(realized) = styles.apply(self.world, Target::Text(&node.0))? {
- let stored = self.scratch.templates.alloc(realized);
- return self.accept(stored, styles);
- }
- } else if let Some(styled) = content.downcast::<StyledNode>() {
- return self.styled(styled, styles);
- } else if let Some(seq) = content.downcast::<SequenceNode>() {
- return self.sequence(seq, styles);
- } else if content.has::<dyn Show>() {
- if self.show(&content, styles)? {
- return Ok(());
- }
- }
-
- if self.list.accept(content, styles) {
- return Ok(());
- }
-
- self.interrupt(Interruption::List, styles, false)?;
-
- if content.is::<ListItem>() {
- self.list.accept(content, styles);
- return Ok(());
- }
-
- if self.par.accept(content, styles) {
- return Ok(());
- }
-
- self.interrupt(Interruption::Par, styles, false)?;
-
- if self.flow.accept(content, styles) {
- return Ok(());
- }
-
- let keep = content.downcast::<PagebreakNode>().map_or(false, |node| !node.weak);
- self.interrupt(Interruption::Page, styles, keep)?;
-
- if let Some(doc) = &mut self.doc {
- doc.accept(content, styles);
- }
-
- // We might want to issue a warning or error for content that wasn't
- // handled (e.g. a pagebreak in a flow building process). However, we
- // don't have the spans here at the moment.
- Ok(())
- }
-
- fn show(&mut self, node: &'a Content, styles: StyleChain<'a>) -> SourceResult<bool> {
- if let Some(mut realized) = styles.apply(self.world, Target::Node(node))? {
- let mut map = StyleMap::new();
- let barrier = Barrier::new(node.id());
- map.push(StyleEntry::Barrier(barrier));
- map.push(StyleEntry::Barrier(barrier));
- realized = realized.styled_with_map(map);
- let stored = self.scratch.templates.alloc(realized);
- self.accept(stored, styles)?;
- Ok(true)
- } else {
- Ok(false)
- }
- }
-
- fn styled(
- &mut self,
- styled: &'a StyledNode,
- styles: StyleChain<'a>,
- ) -> SourceResult<()> {
- let stored = self.scratch.styles.alloc(styles);
- let styles = styled.map.chain(stored);
- let intr = styled.map.interruption();
-
- if let Some(intr) = intr {
- self.interrupt(intr, styles, false)?;
- }
-
- self.accept(&styled.sub, styles)?;
-
- if let Some(intr) = intr {
- self.interrupt(intr, styles, true)?;
- }
-
- Ok(())
- }
-
- fn interrupt(
- &mut self,
- intr: Interruption,
- styles: StyleChain<'a>,
- keep: bool,
- ) -> SourceResult<()> {
- if intr >= Interruption::List && !self.list.is_empty() {
- mem::take(&mut self.list).finish(self)?;
- }
-
- if intr >= Interruption::Par {
- if !self.par.is_empty() {
- mem::take(&mut self.par).finish(self);
- }
- }
-
- if intr >= Interruption::Page {
- if let Some(doc) = &mut self.doc {
- if !self.flow.is_empty() || (doc.keep_next && keep) {
- mem::take(&mut self.flow).finish(doc, styles);
- }
- doc.keep_next = !keep;
- }
- }
-
- Ok(())
- }
-
- fn sequence(
- &mut self,
- seq: &'a SequenceNode,
- styles: StyleChain<'a>,
- ) -> SourceResult<()> {
- for content in &seq.0 {
- self.accept(content, styles)?;
- }
- Ok(())
- }
-}
-
-/// Accepts pagebreaks and pages.
-struct DocBuilder<'a> {
- /// The page runs built so far.
- pages: StyleVecBuilder<'a, PageNode>,
- /// Whether to keep a following page even if it is empty.
- keep_next: bool,
-}
-
-impl<'a> DocBuilder<'a> {
- fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) {
- if let Some(pagebreak) = content.downcast::<PagebreakNode>() {
- self.keep_next = !pagebreak.weak;
- }
-
- if let Some(page) = content.downcast::<PageNode>() {
- self.pages.push(page.clone(), styles);
- self.keep_next = false;
- }
- }
-}
-
-impl Default for DocBuilder<'_> {
- fn default() -> Self {
- Self {
- pages: StyleVecBuilder::new(),
- keep_next: true,
- }
- }
-}
-
-/// Accepts flow content.
-#[derive(Default)]
-struct FlowBuilder<'a>(CollapsingBuilder<'a, FlowChild>);
-
-impl<'a> FlowBuilder<'a> {
- fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
- // Weak flow elements:
- // Weakness | Element
- // 0 | weak colbreak
- // 1 | weak fractional spacing
- // 2 | weak spacing
- // 3 | generated weak spacing
- // 4 | generated weak fractional spacing
- // 5 | par spacing
-
- if let Some(_) = content.downcast::<ParbreakNode>() {
- /* Nothing to do */
- } else if let Some(colbreak) = content.downcast::<ColbreakNode>() {
- if colbreak.weak {
- self.0.weak(FlowChild::Colbreak, styles, 0);
- } else {
- self.0.destructive(FlowChild::Colbreak, styles);
- }
- } else if let Some(vertical) = content.downcast::<VNode>() {
- let child = FlowChild::Spacing(vertical.amount);
- let frac = vertical.amount.is_fractional();
- if vertical.weak {
- let weakness = 1 + u8::from(frac) + 2 * u8::from(vertical.generated);
- self.0.weak(child, styles, weakness);
- } else if frac {
- self.0.destructive(child, styles);
- } else {
- self.0.ignorant(child, styles);
- }
- } else if content.has::<dyn Layout>() {
- let child = FlowChild::Node(content.clone());
- if content.is::<PlaceNode>() {
- self.0.ignorant(child, styles);
- } else {
- self.0.supportive(child, styles);
- }
- } else {
- return false;
- }
-
- true
- }
-
- fn par(&mut self, par: ParNode, styles: StyleChain<'a>, indent: bool) {
- let amount = if indent && !styles.get(ParNode::SPACING_AND_INDENT) {
- styles.get(ParNode::LEADING).into()
- } else {
- styles.get(ParNode::SPACING).into()
- };
-
- self.0.weak(FlowChild::Spacing(amount), styles, 5);
- self.0.supportive(FlowChild::Node(par.pack()), styles);
- self.0.weak(FlowChild::Spacing(amount), styles, 5);
- }
-
- fn finish(self, doc: &mut DocBuilder<'a>, styles: StyleChain<'a>) {
- let (flow, shared) = self.0.finish();
- let styles = if flow.is_empty() { styles } else { shared };
- let node = PageNode(FlowNode(flow).pack());
- doc.pages.push(node, styles);
- }
-
- fn is_empty(&self) -> bool {
- self.0.is_empty()
- }
-}
-
-/// Accepts paragraph content.
-#[derive(Default)]
-struct ParBuilder<'a>(CollapsingBuilder<'a, ParChild>);
-
-impl<'a> ParBuilder<'a> {
- fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
- // Weak par elements:
- // Weakness | Element
- // 0 | weak fractional spacing
- // 1 | weak spacing
- // 2 | space
-
- if content.is::<SpaceNode>() {
- self.0.weak(ParChild::Text(' '.into()), styles, 2);
- } else if let Some(linebreak) = content.downcast::<LinebreakNode>() {
- let c = if linebreak.justify { '\u{2028}' } else { '\n' };
- self.0.destructive(ParChild::Text(c.into()), styles);
- } else if let Some(horizontal) = content.downcast::<HNode>() {
- let child = ParChild::Spacing(horizontal.amount);
- let frac = horizontal.amount.is_fractional();
- if horizontal.weak {
- let weakness = u8::from(!frac);
- self.0.weak(child, styles, weakness);
- } else if frac {
- self.0.destructive(child, styles);
- } else {
- self.0.ignorant(child, styles);
- }
- } else if let Some(quote) = content.downcast::<SmartQuoteNode>() {
- self.0.supportive(ParChild::Quote { double: quote.double }, styles);
- } else if let Some(node) = content.downcast::<TextNode>() {
- self.0.supportive(ParChild::Text(node.0.clone()), styles);
- } else if let Some(node) = content.to::<dyn Layout>() {
- if node.level() == Level::Inline {
- self.0.supportive(ParChild::Node(content.clone()), styles);
- } else {
- return false;
- }
- } else {
- return false;
- }
-
- true
- }
-
- fn finish(self, parent: &mut Builder<'a>) {
- let (mut children, shared) = self.0.finish();
- if children.is_empty() {
- return;
- }
-
- // Paragraph indent should only apply if the paragraph starts with
- // text and follows directly after another paragraph.
- let indent = shared.get(ParNode::INDENT);
- if !indent.is_zero()
- && children
- .items()
- .find_map(|child| match child {
- ParChild::Spacing(_) => None,
- ParChild::Text(_) | ParChild::Quote { .. } => Some(true),
- ParChild::Node(_) => Some(false),
- })
- .unwrap_or_default()
- && parent
- .flow
- .0
- .items()
- .rev()
- .find_map(|child| match child {
- FlowChild::Spacing(_) => None,
- FlowChild::Node(node) => Some(node.is::<ParNode>()),
- FlowChild::Colbreak => Some(false),
- })
- .unwrap_or_default()
- {
- children.push_front(ParChild::Spacing(indent.into()));
- }
-
- parent.flow.par(ParNode(children), shared, !indent.is_zero());
- }
-
- fn is_empty(&self) -> bool {
- self.0.is_empty()
- }
-}
-
-/// Accepts list / enum items, spaces, paragraph breaks.
-struct ListBuilder<'a> {
- /// The list items collected so far.
- items: StyleVecBuilder<'a, ListItem>,
- /// Whether the list contains no paragraph breaks.
- tight: bool,
- /// Whether the list can be attached.
- attachable: bool,
- /// Trailing content for which it is unclear whether it is part of the list.
- staged: Vec<(&'a Content, StyleChain<'a>)>,
-}
-
-impl<'a> ListBuilder<'a> {
- fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
- if self.items.is_empty() {
- if content.is::<ParbreakNode>() {
- self.attachable = false;
- } else if !content.is::<SpaceNode>() && !content.is::<ListItem>() {
- self.attachable = true;
- }
- }
-
- if let Some(item) = content.downcast::<ListItem>() {
- if self
- .items
- .items()
- .next()
- .map_or(true, |first| item.kind() == first.kind())
- {
- self.items.push(item.clone(), styles);
- self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakNode>());
- } else {
- return false;
- }
- } else if !self.items.is_empty()
- && (content.is::<SpaceNode>() || content.is::<ParbreakNode>())
- {
- self.staged.push((content, styles));
- } else {
- return false;
- }
-
- true
- }
-
- fn finish(self, parent: &mut Builder<'a>) -> SourceResult<()> {
- let (items, shared) = self.items.finish();
- let kind = match items.items().next() {
- Some(item) => item.kind(),
- None => return Ok(()),
- };
-
- let tight = self.tight;
- let attached = tight && self.attachable;
- let content = match kind {
- LIST => ListNode::<LIST> { tight, attached, items }.pack(),
- ENUM => ListNode::<ENUM> { tight, attached, items }.pack(),
- DESC | _ => ListNode::<DESC> { tight, attached, items }.pack(),
- };
-
- let stored = parent.scratch.templates.alloc(content);
- parent.accept(stored, shared)?;
-
- for (content, styles) in self.staged {
- parent.accept(content, styles)?;
- }
-
- parent.list.attachable = true;
-
- Ok(())
- }
-
- fn is_empty(&self) -> bool {
- self.items.is_empty()
- }
-}
-
-impl Default for ListBuilder<'_> {
- fn default() -> Self {
- Self {
- items: StyleVecBuilder::default(),
- tight: true,
- attachable: true,
- staged: vec![],
- }
- }
-}
diff --git a/src/model/recipe.rs b/src/model/recipe.rs
deleted file mode 100644
index c687f4fb..00000000
--- a/src/model/recipe.rs
+++ /dev/null
@@ -1,185 +0,0 @@
-use std::fmt::{self, Debug, Formatter};
-use std::hash::Hash;
-
-use comemo::Tracked;
-
-use super::{
- Args, Capability, Content, Func, Interruption, Node, NodeId, Regex, StyleChain,
- StyleEntry, Value,
-};
-use crate::diag::SourceResult;
-use crate::library::structure::{DescNode, EnumNode, ListNode};
-use crate::library::text::TextNode;
-use crate::syntax::Spanned;
-use crate::World;
-
-/// A show rule recipe.
-#[derive(Clone, PartialEq, Hash)]
-pub struct Recipe {
- /// The patterns to customize.
- pub pattern: Pattern,
- /// The function that defines the recipe.
- pub func: Spanned<Func>,
-}
-
-impl Recipe {
- /// Whether the recipe is applicable to the target.
- pub fn applicable(&self, target: Target) -> bool {
- match (&self.pattern, target) {
- (Pattern::Node(id), Target::Node(node)) => *id == node.id(),
- (Pattern::Regex(_), Target::Text(_)) => true,
- _ => false,
- }
- }
-
- /// Try to apply the recipe to the target.
- pub fn apply(
- &self,
- world: Tracked<dyn World>,
- sel: Selector,
- target: Target,
- ) -> SourceResult<Option<Content>> {
- let content = match (target, &self.pattern) {
- (Target::Node(node), &Pattern::Node(id)) if node.id() == id => {
- let node = node.to::<dyn Show>().unwrap().unguard_parts(sel);
- self.call(world, || Value::Content(node))?
- }
-
- (Target::Text(text), Pattern::Regex(regex)) => {
- let mut result = vec![];
- let mut cursor = 0;
-
- for mat in regex.find_iter(text) {
- let start = mat.start();
- if cursor < start {
- result.push(TextNode(text[cursor .. start].into()).pack());
- }
-
- result.push(self.call(world, || Value::Str(mat.as_str().into()))?);
- cursor = mat.end();
- }
-
- if result.is_empty() {
- return Ok(None);
- }
-
- if cursor < text.len() {
- result.push(TextNode(text[cursor ..].into()).pack());
- }
-
- Content::sequence(result)
- }
-
- _ => return Ok(None),
- };
-
- Ok(Some(content.styled_with_entry(StyleEntry::Guard(sel))))
- }
-
- /// Call the recipe function, with the argument if desired.
- fn call<F>(&self, world: Tracked<dyn World>, arg: F) -> SourceResult<Content>
- where
- F: FnOnce() -> Value,
- {
- let args = if self.func.v.argc() == Some(0) {
- Args::new(self.func.span, [])
- } else {
- Args::new(self.func.span, [arg()])
- };
-
- Ok(self.func.v.call_detached(world, args)?.display(world))
- }
-
- /// What kind of structure the property interrupts.
- pub fn interruption(&self) -> Option<Interruption> {
- if let Pattern::Node(id) = self.pattern {
- if id == NodeId::of::<ListNode>()
- || id == NodeId::of::<EnumNode>()
- || id == NodeId::of::<DescNode>()
- {
- return Some(Interruption::List);
- }
- }
-
- None
- }
-}
-
-impl Debug for Recipe {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(
- f,
- "Recipe matching {:?} from {:?}",
- self.pattern, self.func.span
- )
- }
-}
-
-/// A show rule pattern that may match a target.
-#[derive(Debug, Clone, PartialEq, Hash)]
-pub enum Pattern {
- /// Defines the appearence of some node.
- Node(NodeId),
- /// Defines text to be replaced.
- Regex(Regex),
-}
-
-impl Pattern {
- /// Define a simple text replacement pattern.
- pub fn text(text: &str) -> Self {
- Self::Regex(Regex::new(&regex::escape(text)).unwrap())
- }
-}
-
-/// A target for a show rule recipe.
-#[derive(Debug, Copy, Clone, PartialEq)]
-pub enum Target<'a> {
- /// A showable node.
- Node(&'a Content),
- /// A slice of text.
- Text(&'a str),
-}
-
-/// Identifies a show rule recipe.
-#[derive(Debug, Copy, Clone, PartialEq, Hash)]
-pub enum Selector {
- /// The nth recipe from the top of the chain.
- Nth(usize),
- /// The base recipe for a kind of node.
- Base(NodeId),
-}
-
-/// A node that can be realized given some styles.
-pub trait Show: 'static + Sync + Send {
- /// Unguard nested content against recursive show rules.
- fn unguard_parts(&self, sel: Selector) -> Content;
-
- /// Access a field on this node.
- fn field(&self, name: &str) -> Option<Value>;
-
- /// The base recipe for this node that is executed if there is no
- /// user-defined show rule.
- fn realize(
- &self,
- world: Tracked<dyn World>,
- styles: StyleChain,
- ) -> SourceResult<Content>;
-
- /// Finalize this node given the realization of a base or user recipe. Use
- /// this for effects that should work even in the face of a user-defined
- /// show rule, for example:
- /// - Application of general settable properties
- ///
- /// Defaults to just the realized content.
- #[allow(unused_variables)]
- fn finalize(
- &self,
- world: Tracked<dyn World>,
- styles: StyleChain,
- realized: Content,
- ) -> SourceResult<Content> {
- Ok(realized)
- }
-}
-
-impl Capability for dyn Show {}
diff --git a/src/model/resolve.rs b/src/model/resolve.rs
deleted file mode 100644
index 1ca4be69..00000000
--- a/src/model/resolve.rs
+++ /dev/null
@@ -1,84 +0,0 @@
-use super::{Smart, StyleChain};
-use crate::geom::{Abs, Axes, Corners, Em, Length, Numeric, Rel, Sides};
-use crate::library::text::TextNode;
-
-/// A property that is resolved with other properties from the style chain.
-pub trait Resolve {
- /// The type of the resolved output.
- type Output;
-
- /// Resolve the value using the style chain.
- fn resolve(self, styles: StyleChain) -> Self::Output;
-}
-
-impl Resolve for Em {
- type Output = Abs;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- if self.is_zero() {
- Abs::zero()
- } else {
- self.at(styles.get(TextNode::SIZE))
- }
- }
-}
-
-impl Resolve for Length {
- type Output = Abs;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- self.abs + self.em.resolve(styles)
- }
-}
-
-impl<T: Resolve> Resolve for Option<T> {
- type Output = Option<T::Output>;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- self.map(|v| v.resolve(styles))
- }
-}
-
-impl<T: Resolve> Resolve for Smart<T> {
- type Output = Smart<T::Output>;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- self.map(|v| v.resolve(styles))
- }
-}
-
-impl<T: Resolve> Resolve for Axes<T> {
- type Output = Axes<T::Output>;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- self.map(|v| v.resolve(styles))
- }
-}
-
-impl<T: Resolve> Resolve for Sides<T> {
- type Output = Sides<T::Output>;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- self.map(|v| v.resolve(styles))
- }
-}
-
-impl<T: Resolve> Resolve for Corners<T> {
- type Output = Corners<T::Output>;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- self.map(|v| v.resolve(styles))
- }
-}
-
-impl<T> Resolve for Rel<T>
-where
- T: Resolve + Numeric,
- <T as Resolve>::Output: Numeric,
-{
- type Output = Rel<<T as Resolve>::Output>;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- self.map(|abs| abs.resolve(styles))
- }
-}
diff --git a/src/model/str.rs b/src/model/str.rs
index 62b37845..843da9a8 100644
--- a/src/model/str.rs
+++ b/src/model/str.rs
@@ -5,8 +5,9 @@ use std::ops::{Add, AddAssign, Deref};
use unicode_segmentation::UnicodeSegmentation;
-use super::{Array, Dict, RawAlign, Value};
+use super::{Array, Dict, Value};
use crate::diag::StrResult;
+use crate::library::RawAlign;
use crate::util::EcoString;
/// Create a new [`Str`] from a format string.
diff --git a/src/model/styles.rs b/src/model/styles.rs
index d160a4a6..c58a1beb 100644
--- a/src/model/styles.rs
+++ b/src/model/styles.rs
@@ -1,12 +1,20 @@
+use std::any::Any;
use std::fmt::{self, Debug, Formatter};
use std::hash::Hash;
use std::iter;
use std::marker::PhantomData;
+use std::sync::Arc;
-use comemo::Tracked;
+use comemo::{Prehashed, Tracked};
-use super::{Barrier, Content, Key, Property, Recipe, Selector, Show, Target};
+use super::{capability, Args, Content, Func, Node, NodeId, Regex, Smart, Value};
use crate::diag::SourceResult;
+use crate::geom::{Abs, Axes, Corners, Em, Length, Numeric, Rel, Sides};
+use crate::library::layout::PageNode;
+use crate::library::structure::{DescNode, EnumNode, ListNode};
+use crate::library::text::{ParNode, TextNode};
+use crate::syntax::Spanned;
+use crate::util::ReadableTypeId;
use crate::World;
/// A map of style properties.
@@ -618,3 +626,516 @@ impl<'a, T> Default for StyleVecBuilder<'a, T> {
Self::new()
}
}
+
+/// A style property originating from a set rule or constructor.
+#[derive(Clone, Hash)]
+pub struct Property {
+ /// The id of the property's [key](Key).
+ key: KeyId,
+ /// The id of the node the property belongs to.
+ node: NodeId,
+ /// Whether the property should only affect the first node down the
+ /// hierarchy. Used by constructors.
+ scoped: bool,
+ /// The property's value.
+ value: Arc<Prehashed<dyn Bounds>>,
+ /// The name of the property.
+ #[cfg(debug_assertions)]
+ name: &'static str,
+}
+
+impl Property {
+ /// Create a new property from a key-value pair.
+ pub fn new<'a, K: Key<'a>>(_: K, value: K::Value) -> Self {
+ Self {
+ key: KeyId::of::<K>(),
+ node: K::node(),
+ value: Arc::new(Prehashed::new(value)),
+ scoped: false,
+ #[cfg(debug_assertions)]
+ name: K::NAME,
+ }
+ }
+
+ /// Whether this property has the given key.
+ pub fn is<'a, K: Key<'a>>(&self) -> bool {
+ self.key == KeyId::of::<K>()
+ }
+
+ /// Whether this property belongs to the node `T`.
+ pub fn is_of<T: 'static>(&self) -> bool {
+ self.node == NodeId::of::<T>()
+ }
+
+ /// Access the property's value if it is of the given key.
+ pub fn downcast<'a, K: Key<'a>>(&'a self) -> Option<&'a K::Value> {
+ if self.key == KeyId::of::<K>() {
+ (**self.value).as_any().downcast_ref()
+ } else {
+ None
+ }
+ }
+
+ /// The node this property is for.
+ pub fn node(&self) -> NodeId {
+ self.node
+ }
+
+ /// Whether the property is scoped.
+ pub fn scoped(&self) -> bool {
+ self.scoped
+ }
+
+ /// Make the property scoped.
+ pub fn make_scoped(&mut self) {
+ self.scoped = true;
+ }
+
+ /// What kind of structure the property interrupts.
+ pub fn interruption(&self) -> Option<Interruption> {
+ if self.is_of::<PageNode>() {
+ Some(Interruption::Page)
+ } else if self.is_of::<ParNode>() {
+ Some(Interruption::Par)
+ } else if self.is_of::<ListNode>()
+ || self.is_of::<EnumNode>()
+ || self.is_of::<DescNode>()
+ {
+ Some(Interruption::List)
+ } else {
+ None
+ }
+ }
+}
+
+impl Debug for Property {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ #[cfg(debug_assertions)]
+ write!(f, "{} = ", self.name)?;
+ write!(f, "{:?}", self.value)?;
+ if self.scoped {
+ write!(f, " [scoped]")?;
+ }
+ Ok(())
+ }
+}
+
+impl PartialEq for Property {
+ fn eq(&self, other: &Self) -> bool {
+ self.key == other.key
+ && self.value.eq(&other.value)
+ && self.scoped == other.scoped
+ }
+}
+
+trait Bounds: Debug + Sync + Send + 'static {
+ fn as_any(&self) -> &dyn Any;
+}
+
+impl<T> Bounds for T
+where
+ T: Debug + Sync + Send + 'static,
+{
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+}
+
+/// A style property key.
+///
+/// This trait is not intended to be implemented manually, but rather through
+/// the `#[node]` proc-macro.
+pub trait Key<'a>: Copy + 'static {
+ /// The unfolded type which this property is stored as in a style map.
+ type Value: Debug + Clone + Hash + Sync + Send + 'static;
+
+ /// The folded type of value that is returned when reading this property
+ /// from a style chain.
+ type Output;
+
+ /// The name of the property, used for debug printing.
+ const NAME: &'static str;
+
+ /// The id of the node the key belongs to.
+ fn node() -> NodeId;
+
+ /// Compute an output value from a sequence of values belonging to this key,
+ /// folding if necessary.
+ fn get(
+ chain: StyleChain<'a>,
+ values: impl Iterator<Item = &'a Self::Value>,
+ ) -> Self::Output;
+}
+
+/// A unique identifier for a property key.
+#[derive(Copy, Clone, Eq, PartialEq, Hash)]
+struct KeyId(ReadableTypeId);
+
+impl KeyId {
+ /// The id of the given key.
+ pub fn of<'a, T: Key<'a>>() -> Self {
+ Self(ReadableTypeId::of::<T>())
+ }
+}
+
+impl Debug for KeyId {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+/// A scoped property barrier.
+///
+/// Barriers interact with [scoped](super::StyleMap::scoped) styles: A scoped
+/// style can still be read through a single barrier (the one of the node it
+/// _should_ apply to), but a second barrier will make it invisible.
+#[derive(Copy, Clone, Eq, PartialEq, Hash)]
+pub struct Barrier(NodeId);
+
+impl Barrier {
+ /// Create a new barrier for the given node.
+ pub fn new(node: NodeId) -> Self {
+ Self(node)
+ }
+
+ /// Whether this barrier is for the node `T`.
+ pub fn is_for(&self, node: NodeId) -> bool {
+ self.0 == node
+ }
+}
+
+impl Debug for Barrier {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "Barrier for {:?}", self.0)
+ }
+}
+
+/// A property that is resolved with other properties from the style chain.
+pub trait Resolve {
+ /// The type of the resolved output.
+ type Output;
+
+ /// Resolve the value using the style chain.
+ fn resolve(self, styles: StyleChain) -> Self::Output;
+}
+
+impl Resolve for Em {
+ type Output = Abs;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ if self.is_zero() {
+ Abs::zero()
+ } else {
+ self.at(styles.get(TextNode::SIZE))
+ }
+ }
+}
+
+impl Resolve for Length {
+ type Output = Abs;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ self.abs + self.em.resolve(styles)
+ }
+}
+
+impl<T: Resolve> Resolve for Option<T> {
+ type Output = Option<T::Output>;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ self.map(|v| v.resolve(styles))
+ }
+}
+
+impl<T: Resolve> Resolve for Smart<T> {
+ type Output = Smart<T::Output>;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ self.map(|v| v.resolve(styles))
+ }
+}
+
+impl<T: Resolve> Resolve for Axes<T> {
+ type Output = Axes<T::Output>;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ self.map(|v| v.resolve(styles))
+ }
+}
+
+impl<T: Resolve> Resolve for Sides<T> {
+ type Output = Sides<T::Output>;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ self.map(|v| v.resolve(styles))
+ }
+}
+
+impl<T: Resolve> Resolve for Corners<T> {
+ type Output = Corners<T::Output>;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ self.map(|v| v.resolve(styles))
+ }
+}
+
+impl<T> Resolve for Rel<T>
+where
+ T: Resolve + Numeric,
+ <T as Resolve>::Output: Numeric,
+{
+ type Output = Rel<<T as Resolve>::Output>;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ self.map(|abs| abs.resolve(styles))
+ }
+}
+
+/// A property that is folded to determine its final value.
+pub trait Fold {
+ /// The type of the folded output.
+ type Output;
+
+ /// Fold this inner value with an outer folded value.
+ fn fold(self, outer: Self::Output) -> Self::Output;
+}
+
+impl<T> Fold for Option<T>
+where
+ T: Fold,
+ T::Output: Default,
+{
+ type Output = Option<T::Output>;
+
+ fn fold(self, outer: Self::Output) -> Self::Output {
+ self.map(|inner| inner.fold(outer.unwrap_or_default()))
+ }
+}
+
+impl<T> Fold for Smart<T>
+where
+ T: Fold,
+ T::Output: Default,
+{
+ type Output = Smart<T::Output>;
+
+ fn fold(self, outer: Self::Output) -> Self::Output {
+ self.map(|inner| inner.fold(outer.unwrap_or_default()))
+ }
+}
+
+impl<T> Fold for Sides<T>
+where
+ T: Fold,
+{
+ type Output = Sides<T::Output>;
+
+ fn fold(self, outer: Self::Output) -> Self::Output {
+ self.zip(outer, |inner, outer| inner.fold(outer))
+ }
+}
+
+impl Fold for Sides<Option<Rel<Abs>>> {
+ type Output = Sides<Rel<Abs>>;
+
+ fn fold(self, outer: Self::Output) -> Self::Output {
+ self.zip(outer, |inner, outer| inner.unwrap_or(outer))
+ }
+}
+
+impl Fold for Sides<Option<Smart<Rel<Length>>>> {
+ type Output = Sides<Smart<Rel<Length>>>;
+
+ fn fold(self, outer: Self::Output) -> Self::Output {
+ self.zip(outer, |inner, outer| inner.unwrap_or(outer))
+ }
+}
+
+impl<T> Fold for Corners<T>
+where
+ T: Fold,
+{
+ type Output = Corners<T::Output>;
+
+ fn fold(self, outer: Self::Output) -> Self::Output {
+ self.zip(outer, |inner, outer| inner.fold(outer))
+ }
+}
+
+impl Fold for Corners<Option<Rel<Abs>>> {
+ type Output = Corners<Rel<Abs>>;
+
+ fn fold(self, outer: Self::Output) -> Self::Output {
+ self.zip(outer, |inner, outer| inner.unwrap_or(outer))
+ }
+}
+
+/// A show rule recipe.
+#[derive(Clone, PartialEq, Hash)]
+pub struct Recipe {
+ /// The patterns to customize.
+ pub pattern: Pattern,
+ /// The function that defines the recipe.
+ pub func: Spanned<Func>,
+}
+
+impl Recipe {
+ /// Whether the recipe is applicable to the target.
+ pub fn applicable(&self, target: Target) -> bool {
+ match (&self.pattern, target) {
+ (Pattern::Node(id), Target::Node(node)) => *id == node.id(),
+ (Pattern::Regex(_), Target::Text(_)) => true,
+ _ => false,
+ }
+ }
+
+ /// Try to apply the recipe to the target.
+ pub fn apply(
+ &self,
+ world: Tracked<dyn World>,
+ sel: Selector,
+ target: Target,
+ ) -> SourceResult<Option<Content>> {
+ let content = match (target, &self.pattern) {
+ (Target::Node(node), &Pattern::Node(id)) if node.id() == id => {
+ let node = node.to::<dyn Show>().unwrap().unguard_parts(sel);
+ self.call(world, || Value::Content(node))?
+ }
+
+ (Target::Text(text), Pattern::Regex(regex)) => {
+ let mut result = vec![];
+ let mut cursor = 0;
+
+ for mat in regex.find_iter(text) {
+ let start = mat.start();
+ if cursor < start {
+ result.push(TextNode(text[cursor .. start].into()).pack());
+ }
+
+ result.push(self.call(world, || Value::Str(mat.as_str().into()))?);
+ cursor = mat.end();
+ }
+
+ if result.is_empty() {
+ return Ok(None);
+ }
+
+ if cursor < text.len() {
+ result.push(TextNode(text[cursor ..].into()).pack());
+ }
+
+ Content::sequence(result)
+ }
+
+ _ => return Ok(None),
+ };
+
+ Ok(Some(content.styled_with_entry(StyleEntry::Guard(sel))))
+ }
+
+ /// Call the recipe function, with the argument if desired.
+ fn call<F>(&self, world: Tracked<dyn World>, arg: F) -> SourceResult<Content>
+ where
+ F: FnOnce() -> Value,
+ {
+ let args = if self.func.v.argc() == Some(0) {
+ Args::new(self.func.span, [])
+ } else {
+ Args::new(self.func.span, [arg()])
+ };
+
+ Ok(self.func.v.call_detached(world, args)?.display(world))
+ }
+
+ /// What kind of structure the property interrupts.
+ pub fn interruption(&self) -> Option<Interruption> {
+ if let Pattern::Node(id) = self.pattern {
+ if id == NodeId::of::<ListNode>()
+ || id == NodeId::of::<EnumNode>()
+ || id == NodeId::of::<DescNode>()
+ {
+ return Some(Interruption::List);
+ }
+ }
+
+ None
+ }
+}
+
+impl Debug for Recipe {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(
+ f,
+ "Recipe matching {:?} from {:?}",
+ self.pattern, self.func.span
+ )
+ }
+}
+
+/// A show rule pattern that may match a target.
+#[derive(Debug, Clone, PartialEq, Hash)]
+pub enum Pattern {
+ /// Defines the appearence of some node.
+ Node(NodeId),
+ /// Defines text to be replaced.
+ Regex(Regex),
+}
+
+impl Pattern {
+ /// Define a simple text replacement pattern.
+ pub fn text(text: &str) -> Self {
+ Self::Regex(Regex::new(&regex::escape(text)).unwrap())
+ }
+}
+
+/// A target for a show rule recipe.
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub enum Target<'a> {
+ /// A showable node.
+ Node(&'a Content),
+ /// A slice of text.
+ Text(&'a str),
+}
+
+/// Identifies a show rule recipe.
+#[derive(Debug, Copy, Clone, PartialEq, Hash)]
+pub enum Selector {
+ /// The nth recipe from the top of the chain.
+ Nth(usize),
+ /// The base recipe for a kind of node.
+ Base(NodeId),
+}
+
+/// A node that can be realized given some styles.
+#[capability]
+pub trait Show: 'static + Sync + Send {
+ /// Unguard nested content against recursive show rules.
+ fn unguard_parts(&self, sel: Selector) -> Content;
+
+ /// Access a field on this node.
+ fn field(&self, name: &str) -> Option<Value>;
+
+ /// The base recipe for this node that is executed if there is no
+ /// user-defined show rule.
+ fn realize(
+ &self,
+ world: Tracked<dyn World>,
+ styles: StyleChain,
+ ) -> SourceResult<Content>;
+
+ /// Finalize this node given the realization of a base or user recipe. Use
+ /// this for effects that should work even in the face of a user-defined
+ /// show rule, for example:
+ /// - Application of general settable properties
+ ///
+ /// Defaults to just the realized content.
+ #[allow(unused_variables)]
+ fn finalize(
+ &self,
+ world: Tracked<dyn World>,
+ styles: StyleChain,
+ realized: Content,
+ ) -> SourceResult<Content> {
+ Ok(realized)
+ }
+}
diff --git a/src/model/value.rs b/src/model/value.rs
index 4741401c..d68f42a0 100644
--- a/src/model/value.rs
+++ b/src/model/value.rs
@@ -307,7 +307,7 @@ where
}
fn hash128(&self) -> u128 {
- // Also hash the TypeId since nodes with different types but
+ // Also hash the TypeId since values with different types but
// equal data should be different.
let mut state = SipHasher::new();
self.type_id().hash(&mut state);