mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Reorganize style module
This commit is contained in:
parent
c3895cbd66
commit
92f2c56203
@ -99,7 +99,7 @@ impl LayoutBlock for ColumnsNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A column break.
|
/// A column break.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct ColbreakNode {
|
pub struct ColbreakNode {
|
||||||
pub weak: bool,
|
pub weak: bool,
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use super::VNode;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// An inline-level container that sizes content.
|
/// An inline-level container that sizes content.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct BoxNode {
|
pub struct BoxNode {
|
||||||
/// How to size the content horizontally and vertically.
|
/// How to size the content horizontally and vertically.
|
||||||
pub sizing: Axes<Option<Rel<Length>>>,
|
pub sizing: Axes<Option<Rel<Length>>>,
|
||||||
@ -58,7 +58,7 @@ impl LayoutInline for BoxNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A block-level container that places content into a separate flow.
|
/// A block-level container that places content into a separate flow.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct BlockNode(pub Content);
|
pub struct BlockNode(pub Content);
|
||||||
|
|
||||||
#[node(LayoutBlock)]
|
#[node(LayoutBlock)]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use typst::model::{Property, StyleEntry};
|
use typst::model::{Property, Style};
|
||||||
|
|
||||||
use super::{AlignNode, ColbreakNode, PlaceNode, Spacing, VNode};
|
use super::{AlignNode, ColbreakNode, PlaceNode, Spacing, VNode};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@ -161,7 +161,7 @@ impl FlowLayouter {
|
|||||||
let mut chained = styles;
|
let mut chained = styles;
|
||||||
if !self.last_block_was_par && is_par && !styles.get(ParNode::INDENT).is_zero() {
|
if !self.last_block_was_par && is_par && !styles.get(ParNode::INDENT).is_zero() {
|
||||||
let property = Property::new(ParNode::INDENT, Length::zero());
|
let property = Property::new(ParNode::INDENT, Length::zero());
|
||||||
reset = StyleEntry::Property(property);
|
reset = Style::Property(property);
|
||||||
chained = reset.chain(&styles);
|
chained = reset.chain(&styles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,8 +32,8 @@ use typst::diag::SourceResult;
|
|||||||
use typst::frame::Frame;
|
use typst::frame::Frame;
|
||||||
use typst::geom::*;
|
use typst::geom::*;
|
||||||
use typst::model::{
|
use typst::model::{
|
||||||
capability, Content, Node, SequenceNode, Show, StyleChain, StyleEntry,
|
capability, Content, Node, SequenceNode, Show, Style, StyleChain, StyleVecBuilder,
|
||||||
StyleVecBuilder, StyledNode,
|
StyledNode,
|
||||||
};
|
};
|
||||||
use typst::World;
|
use typst::World;
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ impl LayoutBlock for Content {
|
|||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
if !self.has::<dyn Show>() || !styles.applicable(self) {
|
if !self.has::<dyn Show>() || !styles.applicable(self) {
|
||||||
if let Some(node) = self.to::<dyn LayoutBlock>() {
|
if let Some(node) = self.to::<dyn LayoutBlock>() {
|
||||||
let barrier = StyleEntry::Barrier(self.id());
|
let barrier = Style::Barrier(self.id());
|
||||||
let styles = barrier.chain(&styles);
|
let styles = barrier.chain(&styles);
|
||||||
return node.layout_block(world, regions, styles);
|
return node.layout_block(world, regions, styles);
|
||||||
}
|
}
|
||||||
@ -128,13 +128,13 @@ impl LayoutInline for Content {
|
|||||||
|
|
||||||
if !self.has::<dyn Show>() || !styles.applicable(self) {
|
if !self.has::<dyn Show>() || !styles.applicable(self) {
|
||||||
if let Some(node) = self.to::<dyn LayoutInline>() {
|
if let Some(node) = self.to::<dyn LayoutInline>() {
|
||||||
let barrier = StyleEntry::Barrier(self.id());
|
let barrier = Style::Barrier(self.id());
|
||||||
let styles = barrier.chain(&styles);
|
let styles = barrier.chain(&styles);
|
||||||
return node.layout_inline(world, regions, styles);
|
return node.layout_inline(world, regions, styles);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(node) = self.to::<dyn LayoutBlock>() {
|
if let Some(node) = self.to::<dyn LayoutBlock>() {
|
||||||
let barrier = StyleEntry::Barrier(self.id());
|
let barrier = Style::Barrier(self.id());
|
||||||
let styles = barrier.chain(&styles);
|
let styles = barrier.chain(&styles);
|
||||||
return Ok(node.layout_block(world, regions, styles)?.remove(0));
|
return Ok(node.layout_block(world, regions, styles)?.remove(0));
|
||||||
}
|
}
|
||||||
|
@ -407,7 +407,7 @@ impl Fold for FontFeatures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A text space.
|
/// A text space.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct SpaceNode;
|
pub struct SpaceNode;
|
||||||
|
|
||||||
#[node(Behave)]
|
#[node(Behave)]
|
||||||
@ -424,7 +424,7 @@ impl Behave for SpaceNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A line break.
|
/// A line break.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct LinebreakNode {
|
pub struct LinebreakNode {
|
||||||
pub justify: bool,
|
pub justify: bool,
|
||||||
}
|
}
|
||||||
@ -444,7 +444,7 @@ impl Behave for LinebreakNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A smart quote.
|
/// A smart quote.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct SmartQuoteNode {
|
pub struct SmartQuoteNode {
|
||||||
pub double: bool,
|
pub double: bool,
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ castable! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A paragraph break.
|
/// A paragraph break.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct ParbreakNode;
|
pub struct ParbreakNode;
|
||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
|
@ -9,7 +9,7 @@ use comemo::Tracked;
|
|||||||
use siphasher::sip128::{Hasher128, SipHasher};
|
use siphasher::sip128::{Hasher128, SipHasher};
|
||||||
use typst_macros::node;
|
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::diag::{SourceResult, StrResult};
|
||||||
use crate::util::ReadableTypeId;
|
use crate::util::ReadableTypeId;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
@ -95,7 +95,7 @@ impl Content {
|
|||||||
|
|
||||||
/// Style this content with a single style property.
|
/// Style this content with a single style property.
|
||||||
pub fn styled<'k, K: Key<'k>>(self, key: K, value: K::Value) -> Self {
|
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.
|
/// Style this content with a recipe, eagerly applying it if possible.
|
||||||
@ -107,12 +107,12 @@ impl Content {
|
|||||||
if recipe.selector.is_none() {
|
if recipe.selector.is_none() {
|
||||||
recipe.transform.apply(world, recipe.span, || Value::Content(self))
|
recipe.transform.apply(world, recipe.span, || Value::Content(self))
|
||||||
} else {
|
} else {
|
||||||
Ok(self.styled_with_entry(StyleEntry::Recipe(recipe)))
|
Ok(self.styled_with_entry(Style::Recipe(recipe)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Style this content with a style entry.
|
/// 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>() {
|
if let Some(styled) = self.try_downcast_mut::<StyledNode>() {
|
||||||
styled.map.apply(entry);
|
styled.map.apply(entry);
|
||||||
self
|
self
|
||||||
@ -141,7 +141,7 @@ impl Content {
|
|||||||
|
|
||||||
/// Reenable a specific show rule recipe.
|
/// Reenable a specific show rule recipe.
|
||||||
pub fn unguard(&self, id: RecipeId) -> Self {
|
pub fn unguard(&self, id: RecipeId) -> Self {
|
||||||
self.clone().styled_with_entry(StyleEntry::Unguard(id))
|
self.clone().styled_with_entry(Style::Unguard(id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ use crate::World;
|
|||||||
|
|
||||||
/// A map of style properties.
|
/// A map of style properties.
|
||||||
#[derive(Default, Clone, PartialEq, Hash)]
|
#[derive(Default, Clone, PartialEq, Hash)]
|
||||||
pub struct StyleMap(Vec<StyleEntry>);
|
pub struct StyleMap(Vec<Style>);
|
||||||
|
|
||||||
impl StyleMap {
|
impl StyleMap {
|
||||||
/// Create a new, empty style map.
|
/// Create a new, empty style map.
|
||||||
@ -31,11 +31,6 @@ impl StyleMap {
|
|||||||
self.0.is_empty()
|
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.
|
/// Create a style map from a single property-value pair.
|
||||||
pub fn with<'a, K: Key<'a>>(key: K, value: K::Value) -> Self {
|
pub fn with<'a, K: Key<'a>>(key: K, value: K::Value) -> Self {
|
||||||
let mut styles = Self::new();
|
let mut styles = Self::new();
|
||||||
@ -49,7 +44,7 @@ impl StyleMap {
|
|||||||
/// style map, `self` contributes the outer values and `value` is the inner
|
/// style map, `self` contributes the outer values and `value` is the inner
|
||||||
/// one.
|
/// one.
|
||||||
pub fn set<'a, K: Key<'a>>(&mut self, key: K, value: K::Value) {
|
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(_)`.
|
/// 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
|
/// Like [`chain`](Self::chain) or [`apply_map`](Self::apply_map), but with
|
||||||
/// only a entry.
|
/// only a entry.
|
||||||
pub fn apply(&mut self, entry: StyleEntry) {
|
pub fn apply(&mut self, entry: Style) {
|
||||||
if let StyleEntry::Guard(a) = &entry {
|
|
||||||
if let [StyleEntry::Unguard(b), ..] = self.0.as_slice() {
|
|
||||||
if a == b {
|
|
||||||
self.0.remove(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.0.insert(0, entry);
|
self.0.insert(0, entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +98,7 @@ impl StyleMap {
|
|||||||
/// [constructors](super::Node::construct).
|
/// [constructors](super::Node::construct).
|
||||||
pub fn scoped(mut self) -> Self {
|
pub fn scoped(mut self) -> Self {
|
||||||
for entry in &mut self.0 {
|
for entry in &mut self.0 {
|
||||||
if let StyleEntry::Property(property) = entry {
|
if let Style::Property(property) = entry {
|
||||||
property.make_scoped();
|
property.make_scoped();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,8 +111,8 @@ impl StyleMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<StyleEntry> for StyleMap {
|
impl From<Style> for StyleMap {
|
||||||
fn from(entry: StyleEntry) -> Self {
|
fn from(entry: Style) -> Self {
|
||||||
Self(vec![entry])
|
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)]
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
pub enum StyleEntry {
|
pub enum Style {
|
||||||
/// A style property originating from a set rule or constructor.
|
/// A style property originating from a set rule or constructor.
|
||||||
Property(Property),
|
Property(Property),
|
||||||
/// A show rule recipe.
|
/// A show rule recipe.
|
||||||
@ -155,13 +141,13 @@ pub enum StyleEntry {
|
|||||||
Unguard(RecipeId),
|
Unguard(RecipeId),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyleEntry {
|
impl Style {
|
||||||
/// Make this style the first link of the `tail` chain.
|
/// Make this style the first link of the `tail` chain.
|
||||||
pub fn chain<'a>(&'a self, tail: &'a StyleChain) -> StyleChain<'a> {
|
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
|
if !tail
|
||||||
.entries()
|
.entries()
|
||||||
.filter_map(StyleEntry::property)
|
.filter_map(Style::property)
|
||||||
.any(|p| p.scoped() && *id == p.node())
|
.any(|p| p.scoped() && *id == p.node())
|
||||||
{
|
{
|
||||||
return *tail;
|
return *tail;
|
||||||
@ -197,7 +183,7 @@ impl StyleEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for StyleEntry {
|
impl Debug for Style {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.write_str("#[")?;
|
f.write_str("#[")?;
|
||||||
match self {
|
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(®ex::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 chain of style maps, similar to a linked list.
|
||||||
///
|
///
|
||||||
/// A style chain allows to combine properties from multiple style maps in a
|
/// 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)]
|
#[derive(Default, Clone, Copy, Hash)]
|
||||||
pub struct StyleChain<'a> {
|
pub struct StyleChain<'a> {
|
||||||
/// The first link of this chain.
|
/// The first link of this chain.
|
||||||
head: &'a [StyleEntry],
|
head: &'a [Style],
|
||||||
/// The remaining links in the chain.
|
/// The remaining links in the chain.
|
||||||
tail: Option<&'a Self>,
|
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.
|
// Find out how many recipes there any and whether any of them match.
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
let mut any = true;
|
let mut any = true;
|
||||||
for recipe in self.entries().filter_map(StyleEntry::recipe) {
|
for recipe in self.entries().filter_map(Style::recipe) {
|
||||||
n += 1;
|
n += 1;
|
||||||
any |= recipe.applicable(target);
|
any |= recipe.applicable(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find an applicable recipe.
|
// Find an applicable recipe.
|
||||||
if any {
|
if any {
|
||||||
for recipe in self.entries().filter_map(StyleEntry::recipe) {
|
for recipe in self.entries().filter_map(Style::recipe) {
|
||||||
if recipe.applicable(target) {
|
if recipe.applicable(target) {
|
||||||
let sel = RecipeId::Nth(n);
|
let sel = RecipeId::Nth(n);
|
||||||
if !self.guarded(sel) {
|
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.
|
// Find out how many recipes there any and whether any of them match.
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
let mut any = true;
|
let mut any = true;
|
||||||
for recipe in self.entries().filter_map(StyleEntry::recipe) {
|
for recipe in self.entries().filter_map(Style::recipe) {
|
||||||
n += 1;
|
n += 1;
|
||||||
any |= recipe.applicable(target);
|
any |= recipe.applicable(target);
|
||||||
}
|
}
|
||||||
@ -290,7 +609,7 @@ impl<'a> StyleChain<'a> {
|
|||||||
let mut realized = None;
|
let mut realized = None;
|
||||||
let mut guarded = false;
|
let mut guarded = false;
|
||||||
if any {
|
if any {
|
||||||
for recipe in self.entries().filter_map(StyleEntry::recipe) {
|
for recipe in self.entries().filter_map(Style::recipe) {
|
||||||
if recipe.applicable(target) {
|
if recipe.applicable(target) {
|
||||||
let sel = RecipeId::Nth(n);
|
let sel = RecipeId::Nth(n);
|
||||||
if self.guarded(sel) {
|
if self.guarded(sel) {
|
||||||
@ -317,7 +636,7 @@ impl<'a> StyleChain<'a> {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.show(world, self)?;
|
.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 {
|
fn guarded(self, sel: RecipeId) -> bool {
|
||||||
for entry in self.entries() {
|
for entry in self.entries() {
|
||||||
match *entry {
|
match *entry {
|
||||||
StyleEntry::Guard(s) if s == sel => return true,
|
Style::Guard(s) if s == sel => return true,
|
||||||
StyleEntry::Unguard(s) if s == sel => return false,
|
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> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
for entry in &mut self.entries {
|
for entry in &mut self.entries {
|
||||||
match entry {
|
match entry {
|
||||||
StyleEntry::Property(property) => {
|
Style::Property(property) => {
|
||||||
if let Some(value) = property.downcast::<K>() {
|
if let Some(value) = property.downcast::<K>() {
|
||||||
if !property.scoped()
|
if !property.scoped()
|
||||||
|| if self.guarded {
|
|| 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;
|
self.barriers += (*id == K::node()) as usize;
|
||||||
}
|
}
|
||||||
StyleEntry::Guard(RecipeId::Base(id)) => {
|
Style::Guard(RecipeId::Base(id)) => {
|
||||||
self.guarded |= *id == K::node();
|
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.
|
/// An iterator over the entries in a style chain.
|
||||||
struct Entries<'a> {
|
struct Entries<'a> {
|
||||||
inner: std::slice::Iter<'a, StyleEntry>,
|
inner: std::slice::Iter<'a, Style>,
|
||||||
links: Links<'a>,
|
links: Links<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for Entries<'a> {
|
impl<'a> Iterator for Entries<'a> {
|
||||||
type Item = &'a StyleEntry;
|
type Item = &'a Style;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
loop {
|
loop {
|
||||||
@ -470,7 +789,7 @@ impl<'a> Iterator for Entries<'a> {
|
|||||||
struct Links<'a>(Option<StyleChain<'a>>);
|
struct Links<'a>(Option<StyleChain<'a>>);
|
||||||
|
|
||||||
impl<'a> Iterator for Links<'a> {
|
impl<'a> Iterator for Links<'a> {
|
||||||
type Item = &'a [StyleEntry];
|
type Item = &'a [Style];
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let StyleChain { head, tail } = self.0?;
|
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.
|
/// A property that is resolved with other properties from the style chain.
|
||||||
pub trait Resolve {
|
pub trait Resolve {
|
||||||
/// The type of the resolved output.
|
/// 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(®ex::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>;
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user