summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-11-18 11:25:36 +0100
committerLaurenz <laurmaedje@gmail.com>2022-11-18 12:49:27 +0100
commit92f2c56203c6528e35ec1feae14e5dd2c2425311 (patch)
tree8438f9fe8e6cd4b91aad3302d9725975a6b50501 /src
parentc3895cbd66688c8d677d5795813432b9a72b3ef9 (diff)
Reorganize style module
Diffstat (limited to 'src')
-rw-r--r--src/model/content.rs10
-rw-r--r--src/model/styles.rs732
2 files changed, 364 insertions, 378 deletions
diff --git a/src/model/content.rs b/src/model/content.rs
index 3851d38d..3cae99b2 100644
--- a/src/model/content.rs
+++ b/src/model/content.rs
@@ -9,7 +9,7 @@ use comemo::Tracked;
use siphasher::sip128::{Hasher128, SipHasher};
use typst_macros::node;
-use super::{Args, Key, Property, Recipe, RecipeId, StyleEntry, StyleMap, Value, Vm};
+use super::{Args, Key, Property, Recipe, RecipeId, Style, StyleMap, Value, Vm};
use crate::diag::{SourceResult, StrResult};
use crate::util::ReadableTypeId;
use crate::World;
@@ -95,7 +95,7 @@ impl Content {
/// Style this content with a single style property.
pub fn styled<'k, K: Key<'k>>(self, key: K, value: K::Value) -> Self {
- self.styled_with_entry(StyleEntry::Property(Property::new(key, value)))
+ self.styled_with_entry(Style::Property(Property::new(key, value)))
}
/// Style this content with a recipe, eagerly applying it if possible.
@@ -107,12 +107,12 @@ impl Content {
if recipe.selector.is_none() {
recipe.transform.apply(world, recipe.span, || Value::Content(self))
} else {
- Ok(self.styled_with_entry(StyleEntry::Recipe(recipe)))
+ Ok(self.styled_with_entry(Style::Recipe(recipe)))
}
}
/// Style this content with a style entry.
- pub fn styled_with_entry(mut self, entry: StyleEntry) -> Self {
+ pub fn styled_with_entry(mut self, entry: Style) -> Self {
if let Some(styled) = self.try_downcast_mut::<StyledNode>() {
styled.map.apply(entry);
self
@@ -141,7 +141,7 @@ impl Content {
/// Reenable a specific show rule recipe.
pub fn unguard(&self, id: RecipeId) -> Self {
- self.clone().styled_with_entry(StyleEntry::Unguard(id))
+ self.clone().styled_with_entry(Style::Unguard(id))
}
}
diff --git a/src/model/styles.rs b/src/model/styles.rs
index 7aa041c7..16c1eab4 100644
--- a/src/model/styles.rs
+++ b/src/model/styles.rs
@@ -18,7 +18,7 @@ use crate::World;
/// A map of style properties.
#[derive(Default, Clone, PartialEq, Hash)]
-pub struct StyleMap(Vec<StyleEntry>);
+pub struct StyleMap(Vec<Style>);
impl StyleMap {
/// Create a new, empty style map.
@@ -31,11 +31,6 @@ impl StyleMap {
self.0.is_empty()
}
- /// Push an arbitary style entry.
- pub fn push(&mut self, style: StyleEntry) {
- self.0.push(style);
- }
-
/// Create a style map from a single property-value pair.
pub fn with<'a, K: Key<'a>>(key: K, value: K::Value) -> Self {
let mut styles = Self::new();
@@ -49,7 +44,7 @@ impl StyleMap {
/// style map, `self` contributes the outer values and `value` is the inner
/// one.
pub fn set<'a, K: Key<'a>>(&mut self, key: K, value: K::Value) {
- self.push(StyleEntry::Property(Property::new(key, value)));
+ self.0.push(Style::Property(Property::new(key, value)));
}
/// Set an inner value for a style property if it is `Some(_)`.
@@ -84,16 +79,7 @@ impl StyleMap {
///
/// Like [`chain`](Self::chain) or [`apply_map`](Self::apply_map), but with
/// only a entry.
- pub fn apply(&mut self, entry: StyleEntry) {
- if let StyleEntry::Guard(a) = &entry {
- if let [StyleEntry::Unguard(b), ..] = self.0.as_slice() {
- if a == b {
- self.0.remove(0);
- return;
- }
- }
- }
-
+ pub fn apply(&mut self, entry: Style) {
self.0.insert(0, entry);
}
@@ -112,7 +98,7 @@ impl StyleMap {
/// [constructors](super::Node::construct).
pub fn scoped(mut self) -> Self {
for entry in &mut self.0 {
- if let StyleEntry::Property(property) = entry {
+ if let Style::Property(property) = entry {
property.make_scoped();
}
}
@@ -125,8 +111,8 @@ impl StyleMap {
}
}
-impl From<StyleEntry> for StyleMap {
- fn from(entry: StyleEntry) -> Self {
+impl From<Style> for StyleMap {
+ fn from(entry: Style) -> Self {
Self(vec![entry])
}
}
@@ -140,9 +126,9 @@ impl Debug for StyleMap {
}
}
-/// An entry for a single style property, recipe or barrier.
+/// A single style property, recipe or barrier.
#[derive(Clone, PartialEq, Hash)]
-pub enum StyleEntry {
+pub enum Style {
/// A style property originating from a set rule or constructor.
Property(Property),
/// A show rule recipe.
@@ -155,13 +141,13 @@ pub enum StyleEntry {
Unguard(RecipeId),
}
-impl StyleEntry {
+impl Style {
/// Make this style the first link of the `tail` chain.
pub fn chain<'a>(&'a self, tail: &'a StyleChain) -> StyleChain<'a> {
- if let StyleEntry::Barrier(id) = self {
+ if let Style::Barrier(id) = self {
if !tail
.entries()
- .filter_map(StyleEntry::property)
+ .filter_map(Style::property)
.any(|p| p.scoped() && *id == p.node())
{
return *tail;
@@ -197,7 +183,7 @@ impl StyleEntry {
}
}
-impl Debug for StyleEntry {
+impl Debug for Style {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("#[")?;
match self {
@@ -211,6 +197,339 @@ impl Debug for StyleEntry {
}
}
+/// 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 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 with the given id.
+ pub fn is_of(&self, node: NodeId) -> bool {
+ self.node == node
+ }
+
+ /// 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;
+ }
+}
+
+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 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 node that can be realized given some styles.
+#[capability]
+pub trait Show: 'static + Sync + Send {
+ /// Unguard nested content against a specific recipe.
+ fn unguard_parts(&self, id: RecipeId) -> Content;
+
+ /// The base recipe for this node that is executed if there is no
+ /// user-defined show rule.
+ fn show(
+ &self,
+ world: Tracked<dyn World>,
+ styles: StyleChain,
+ ) -> SourceResult<Content>;
+}
+
+/// Post-process a node after it was realized.
+#[capability]
+pub trait Finalize: 'static + Sync + Send {
+ /// 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 the linking behaviour of a link node.
+ fn finalize(
+ &self,
+ world: Tracked<dyn World>,
+ styles: StyleChain,
+ realized: Content,
+ ) -> SourceResult<Content>;
+}
+
+/// A show rule recipe.
+#[derive(Clone, PartialEq, Hash)]
+pub struct Recipe {
+ /// The span errors are reported with.
+ pub span: Span,
+ /// Determines whether the recipe applies to a node.
+ pub selector: Option<Selector>,
+ /// The transformation to perform on the match.
+ pub transform: Transform,
+}
+
+impl Recipe {
+ /// Whether the recipe is applicable to the target.
+ pub fn applicable(&self, target: &Content) -> bool {
+ self.selector
+ .as_ref()
+ .map_or(false, |selector| selector.matches(target))
+ }
+
+ /// Try to apply the recipe to the target.
+ pub fn apply(
+ &self,
+ world: Tracked<dyn World>,
+ sel: RecipeId,
+ target: &Content,
+ ) -> SourceResult<Option<Content>> {
+ let content = match &self.selector {
+ Some(Selector::Node(id, _)) => {
+ if target.id() != *id {
+ return Ok(None);
+ }
+
+ self.transform.apply(world, self.span, || {
+ Value::Content(target.to::<dyn Show>().unwrap().unguard_parts(sel))
+ })?
+ }
+
+ Some(Selector::Regex(regex)) => {
+ let Some(text) = item!(text_str)(target) else {
+ return Ok(None);
+ };
+
+ let make = item!(text);
+ let mut result = vec![];
+ let mut cursor = 0;
+
+ for mat in regex.find_iter(text) {
+ let start = mat.start();
+ if cursor < start {
+ result.push(make(text[cursor..start].into()));
+ }
+
+ let transformed = self
+ .transform
+ .apply(world, self.span, || Value::Str(mat.as_str().into()))?;
+ result.push(transformed);
+ cursor = mat.end();
+ }
+
+ if result.is_empty() {
+ return Ok(None);
+ }
+
+ if cursor < text.len() {
+ result.push(make(text[cursor..].into()));
+ }
+
+ Content::sequence(result)
+ }
+
+ None => return Ok(None),
+ };
+
+ Ok(Some(content.styled_with_entry(Style::Guard(sel))))
+ }
+
+ /// Whether this recipe is for the given node.
+ pub fn is_of(&self, node: NodeId) -> bool {
+ match self.selector {
+ Some(Selector::Node(id, _)) => id == node,
+ _ => false,
+ }
+ }
+}
+
+impl Debug for Recipe {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "Recipe matching {:?}", self.selector)
+ }
+}
+
+/// A selector in a show rule.
+#[derive(Debug, Clone, PartialEq, Hash)]
+pub enum Selector {
+ /// Matches a specific type of node.
+ ///
+ /// If there is a dictionary, only nodes with the fields from the
+ /// dictionary match.
+ Node(NodeId, Option<Dict>),
+ /// Matches text through a regular expression.
+ Regex(Regex),
+}
+
+impl Selector {
+ /// Define a simple text selector.
+ pub fn text(text: &str) -> Self {
+ Self::Regex(Regex::new(&regex::escape(text)).unwrap())
+ }
+
+ /// Whether the selector matches for the target.
+ pub fn matches(&self, target: &Content) -> bool {
+ match self {
+ Self::Node(id, dict) => {
+ *id == target.id()
+ && dict
+ .iter()
+ .flat_map(|dict| dict.iter())
+ .all(|(name, value)| target.field(name).as_ref() == Some(value))
+ }
+ Self::Regex(_) => target.id() == item!(text_id),
+ }
+ }
+}
+
+/// A show rule transformation that can be applied to a match.
+#[derive(Debug, Clone, PartialEq, Hash)]
+pub enum Transform {
+ /// Replacement content.
+ Content(Content),
+ /// A function to apply to the match.
+ Func(Func),
+}
+
+impl Transform {
+ /// Apply the transform.
+ pub fn apply<F>(
+ &self,
+ world: Tracked<dyn World>,
+ span: Span,
+ arg: F,
+ ) -> SourceResult<Content>
+ where
+ F: FnOnce() -> Value,
+ {
+ match self {
+ Transform::Content(content) => Ok(content.clone()),
+ Transform::Func(func) => {
+ let args = Args::new(span, [arg()]);
+ Ok(func.call_detached(world, args)?.display(world))
+ }
+ }
+ }
+}
+
+/// Identifies a show rule recipe.
+#[derive(Debug, Copy, Clone, PartialEq, Hash)]
+pub enum RecipeId {
+ /// The nth recipe from the top of the chain.
+ Nth(usize),
+ /// The base recipe for a kind of node.
+ Base(NodeId),
+}
+
/// A chain of style maps, similar to a linked list.
///
/// A style chain allows to combine properties from multiple style maps in a
@@ -221,7 +540,7 @@ impl Debug for StyleEntry {
#[derive(Default, Clone, Copy, Hash)]
pub struct StyleChain<'a> {
/// The first link of this chain.
- head: &'a [StyleEntry],
+ head: &'a [Style],
/// The remaining links in the chain.
tail: Option<&'a Self>,
}
@@ -251,14 +570,14 @@ impl<'a> StyleChain<'a> {
// Find out how many recipes there any and whether any of them match.
let mut n = 0;
let mut any = true;
- for recipe in self.entries().filter_map(StyleEntry::recipe) {
+ for recipe in self.entries().filter_map(Style::recipe) {
n += 1;
any |= recipe.applicable(target);
}
// Find an applicable recipe.
if any {
- for recipe in self.entries().filter_map(StyleEntry::recipe) {
+ for recipe in self.entries().filter_map(Style::recipe) {
if recipe.applicable(target) {
let sel = RecipeId::Nth(n);
if !self.guarded(sel) {
@@ -281,7 +600,7 @@ impl<'a> StyleChain<'a> {
// Find out how many recipes there any and whether any of them match.
let mut n = 0;
let mut any = true;
- for recipe in self.entries().filter_map(StyleEntry::recipe) {
+ for recipe in self.entries().filter_map(Style::recipe) {
n += 1;
any |= recipe.applicable(target);
}
@@ -290,7 +609,7 @@ impl<'a> StyleChain<'a> {
let mut realized = None;
let mut guarded = false;
if any {
- for recipe in self.entries().filter_map(StyleEntry::recipe) {
+ for recipe in self.entries().filter_map(Style::recipe) {
if recipe.applicable(target) {
let sel = RecipeId::Nth(n);
if self.guarded(sel) {
@@ -317,7 +636,7 @@ impl<'a> StyleChain<'a> {
.unwrap()
.show(world, self)?;
- realized = Some(content.styled_with_entry(StyleEntry::Guard(sel)));
+ realized = Some(content.styled_with_entry(Style::Guard(sel)));
}
}
@@ -338,8 +657,8 @@ impl<'a> StyleChain<'a> {
fn guarded(self, sel: RecipeId) -> bool {
for entry in self.entries() {
match *entry {
- StyleEntry::Guard(s) if s == sel => return true,
- StyleEntry::Unguard(s) if s == sel => return false,
+ Style::Guard(s) if s == sel => return true,
+ Style::Unguard(s) if s == sel => return false,
_ => {}
}
}
@@ -416,7 +735,7 @@ impl<'a, K: Key<'a>> Iterator for Values<'a, K> {
fn next(&mut self) -> Option<Self::Item> {
for entry in &mut self.entries {
match entry {
- StyleEntry::Property(property) => {
+ Style::Property(property) => {
if let Some(value) = property.downcast::<K>() {
if !property.scoped()
|| if self.guarded {
@@ -429,10 +748,10 @@ impl<'a, K: Key<'a>> Iterator for Values<'a, K> {
}
}
}
- StyleEntry::Barrier(id) => {
+ Style::Barrier(id) => {
self.barriers += (*id == K::node()) as usize;
}
- StyleEntry::Guard(RecipeId::Base(id)) => {
+ Style::Guard(RecipeId::Base(id)) => {
self.guarded |= *id == K::node();
}
_ => {}
@@ -445,12 +764,12 @@ impl<'a, K: Key<'a>> Iterator for Values<'a, K> {
/// An iterator over the entries in a style chain.
struct Entries<'a> {
- inner: std::slice::Iter<'a, StyleEntry>,
+ inner: std::slice::Iter<'a, Style>,
links: Links<'a>,
}
impl<'a> Iterator for Entries<'a> {
- type Item = &'a StyleEntry;
+ type Item = &'a Style;
fn next(&mut self) -> Option<Self::Item> {
loop {
@@ -470,7 +789,7 @@ impl<'a> Iterator for Entries<'a> {
struct Links<'a>(Option<StyleChain<'a>>);
impl<'a> Iterator for Links<'a> {
- type Item = &'a [StyleEntry];
+ type Item = &'a [Style];
fn next(&mut self) -> Option<Self::Item> {
let StyleChain { head, tail } = self.0?;
@@ -653,147 +972,6 @@ impl<'a, T> Default for StyleVecBuilder<'a, T> {
}
}
-/// 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 with the given id.
- pub fn is_of(&self, node: NodeId) -> bool {
- self.node == node
- }
-
- /// 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;
- }
-}
-
-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 property that is resolved with other properties from the style chain.
pub trait Resolve {
/// The type of the resolved output.
@@ -988,195 +1166,3 @@ impl Fold for PartialStroke<Abs> {
}
}
}
-
-/// A show rule recipe.
-#[derive(Clone, PartialEq, Hash)]
-pub struct Recipe {
- /// The span errors are reported with.
- pub span: Span,
- /// Determines whether the recipe applies to a node.
- pub selector: Option<Selector>,
- /// The transformation to perform on the match.
- pub transform: Transform,
-}
-
-impl Recipe {
- /// Whether the recipe is applicable to the target.
- pub fn applicable(&self, target: &Content) -> bool {
- self.selector
- .as_ref()
- .map_or(false, |selector| selector.matches(target))
- }
-
- /// Try to apply the recipe to the target.
- pub fn apply(
- &self,
- world: Tracked<dyn World>,
- sel: RecipeId,
- target: &Content,
- ) -> SourceResult<Option<Content>> {
- let content = match &self.selector {
- Some(Selector::Node(id, _)) => {
- if target.id() != *id {
- return Ok(None);
- }
-
- self.transform.apply(world, self.span, || {
- Value::Content(target.to::<dyn Show>().unwrap().unguard_parts(sel))
- })?
- }
-
- Some(Selector::Regex(regex)) => {
- let Some(text) = item!(text_str)(target) else {
- return Ok(None);
- };
-
- let make = world.config().items.text;
- let mut result = vec![];
- let mut cursor = 0;
-
- for mat in regex.find_iter(text) {
- let start = mat.start();
- if cursor < start {
- result.push(make(text[cursor..start].into()));
- }
-
- let transformed = self
- .transform
- .apply(world, self.span, || Value::Str(mat.as_str().into()))?;
- result.push(transformed);
- cursor = mat.end();
- }
-
- if result.is_empty() {
- return Ok(None);
- }
-
- if cursor < text.len() {
- result.push(make(text[cursor..].into()));
- }
-
- Content::sequence(result)
- }
-
- None => return Ok(None),
- };
-
- Ok(Some(content.styled_with_entry(StyleEntry::Guard(sel))))
- }
-
- /// Whether this recipe is for the given node.
- pub fn is_of(&self, node: NodeId) -> bool {
- match self.selector {
- Some(Selector::Node(id, _)) => id == node,
- _ => false,
- }
- }
-}
-
-impl Debug for Recipe {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(f, "Recipe matching {:?}", self.selector)
- }
-}
-
-/// A selector in a show rule.
-#[derive(Debug, Clone, PartialEq, Hash)]
-pub enum Selector {
- /// Matches a specific type of node.
- ///
- /// If there is a dictionary, only nodes with the fields from the
- /// dictionary match.
- Node(NodeId, Option<Dict>),
- /// Matches text through a regular expression.
- Regex(Regex),
-}
-
-impl Selector {
- /// Define a simple text selector.
- pub fn text(text: &str) -> Self {
- Self::Regex(Regex::new(&regex::escape(text)).unwrap())
- }
-
- /// Whether the selector matches for the target.
- pub fn matches(&self, target: &Content) -> bool {
- match self {
- Self::Node(id, dict) => {
- *id == target.id()
- && dict
- .iter()
- .flat_map(|dict| dict.iter())
- .all(|(name, value)| target.field(name).as_ref() == Some(value))
- }
- Self::Regex(_) => target.id() == item!(text_id),
- }
- }
-}
-
-/// A show rule transformation that can be applied to a match.
-#[derive(Debug, Clone, PartialEq, Hash)]
-pub enum Transform {
- /// Replacement content.
- Content(Content),
- /// A function to apply to the match.
- Func(Func),
-}
-
-impl Transform {
- /// Apply the transform.
- pub fn apply<F>(
- &self,
- world: Tracked<dyn World>,
- span: Span,
- arg: F,
- ) -> SourceResult<Content>
- where
- F: FnOnce() -> Value,
- {
- match self {
- Transform::Content(content) => Ok(content.clone()),
- Transform::Func(func) => {
- let args = Args::new(span, [arg()]);
- Ok(func.call_detached(world, args)?.display(world))
- }
- }
- }
-}
-
-/// Identifies a show rule recipe.
-#[derive(Debug, Copy, Clone, PartialEq, Hash)]
-pub enum RecipeId {
- /// 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 a specific recipe.
- fn unguard_parts(&self, id: RecipeId) -> Content;
-
- /// The base recipe for this node that is executed if there is no
- /// user-defined show rule.
- fn show(
- &self,
- world: Tracked<dyn World>,
- styles: StyleChain,
- ) -> SourceResult<Content>;
-}
-
-/// Post-process a node after it was realized.
-#[capability]
-pub trait Finalize: 'static + Sync + Send {
- /// 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 the linking behaviour of a link node.
- fn finalize(
- &self,
- world: Tracked<dyn World>,
- styles: StyleChain,
- realized: Content,
- ) -> SourceResult<Content>;
-}