summaryrefslogtreecommitdiff
path: root/crates/typst-library/src/foundations
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2025-07-09 10:16:36 +0200
committerGitHub <noreply@github.com>2025-07-09 08:16:36 +0000
commite5e1dcd9c01341d2cd3473ac94a70223d5966086 (patch)
treed648efad9463cb10270d55ba35210eeb1e91ee22 /crates/typst-library/src/foundations
parent0a3c6939dd274f40672484695d909c2cc0d0d755 (diff)
Target-specific native show rules (#6569)
Diffstat (limited to 'crates/typst-library/src/foundations')
-rw-r--r--crates/typst-library/src/foundations/content/element.rs6
-rw-r--r--crates/typst-library/src/foundations/context.rs17
-rw-r--r--crates/typst-library/src/foundations/styles.rs129
-rw-r--r--crates/typst-library/src/foundations/target.rs2
4 files changed, 136 insertions, 18 deletions
diff --git a/crates/typst-library/src/foundations/content/element.rs b/crates/typst-library/src/foundations/content/element.rs
index 49b0b0f9..65c2e28b 100644
--- a/crates/typst-library/src/foundations/content/element.rs
+++ b/crates/typst-library/src/foundations/content/element.rs
@@ -246,12 +246,6 @@ pub trait Synthesize {
-> SourceResult<()>;
}
-/// Defines a built-in show rule for an element.
-pub trait Show {
- /// Execute the base recipe for this element.
- fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content>;
-}
-
/// Defines built-in show set rules for an element.
///
/// This is a bit more powerful than a user-defined show-set because it can
diff --git a/crates/typst-library/src/foundations/context.rs b/crates/typst-library/src/foundations/context.rs
index bf4bdcd2..56d87775 100644
--- a/crates/typst-library/src/foundations/context.rs
+++ b/crates/typst-library/src/foundations/context.rs
@@ -3,7 +3,7 @@ use comemo::Track;
use crate::diag::{bail, Hint, HintedStrResult, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
- elem, Args, Construct, Content, Func, Packed, Show, StyleChain, Value,
+ elem, Args, Construct, Content, Func, ShowFn, StyleChain, Value,
};
use crate::introspection::{Locatable, Location};
@@ -61,7 +61,7 @@ fn require<T>(val: Option<T>) -> HintedStrResult<T> {
}
/// Executes a `context` block.
-#[elem(Construct, Locatable, Show)]
+#[elem(Construct, Locatable)]
pub struct ContextElem {
/// The function to call with the context.
#[required]
@@ -75,11 +75,8 @@ impl Construct for ContextElem {
}
}
-impl Show for Packed<ContextElem> {
- #[typst_macros::time(name = "context", span = self.span())]
- fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
- let loc = self.location().unwrap();
- let context = Context::new(Some(loc), Some(styles));
- Ok(self.func.call::<[Value; 0]>(engine, context.track(), [])?.display())
- }
-}
+pub const CONTEXT_RULE: ShowFn<ContextElem> = |elem, engine, styles| {
+ let loc = elem.location().unwrap();
+ let context = Context::new(Some(loc), Some(styles));
+ Ok(elem.func.call::<[Value; 0]>(engine, context.track(), [])?.display())
+};
diff --git a/crates/typst-library/src/foundations/styles.rs b/crates/typst-library/src/foundations/styles.rs
index 978b47d5..0da036f6 100644
--- a/crates/typst-library/src/foundations/styles.rs
+++ b/crates/typst-library/src/foundations/styles.rs
@@ -1,4 +1,5 @@
use std::any::{Any, TypeId};
+use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher};
use std::{mem, ptr};
@@ -13,7 +14,7 @@ use crate::diag::{SourceResult, Trace, Tracepoint};
use crate::engine::Engine;
use crate::foundations::{
cast, ty, Content, Context, Element, Field, Func, NativeElement, OneOrMultiple,
- RefableProperty, Repr, Selector, SettableProperty,
+ Packed, RefableProperty, Repr, Selector, SettableProperty, Target,
};
use crate::text::{FontFamily, FontList, TextElem};
@@ -938,3 +939,129 @@ fn block_wrong_type(func: Element, id: u8, value: &Block) -> ! {
value
)
}
+
+/// Holds native show rules.
+pub struct NativeRuleMap {
+ rules: HashMap<(Element, Target), NativeShowRule>,
+}
+
+/// The signature of a native show rule.
+pub type ShowFn<T> = fn(
+ elem: &Packed<T>,
+ engine: &mut Engine,
+ styles: StyleChain,
+) -> SourceResult<Content>;
+
+impl NativeRuleMap {
+ /// Creates a new rule map.
+ ///
+ /// Should be populated with rules for all target-element combinations that
+ /// are supported.
+ ///
+ /// Contains built-in rules for a few special elements.
+ pub fn new() -> Self {
+ let mut rules = Self { rules: HashMap::new() };
+
+ // ContextElem is as special as SequenceElem and StyledElem and could,
+ // in theory, also be special cased in realization.
+ rules.register_builtin(crate::foundations::CONTEXT_RULE);
+
+ // CounterDisplayElem only exists because the compiler can't currently
+ // express the equivalent of `context counter(..).display(..)` in native
+ // code (no native closures).
+ rules.register_builtin(crate::introspection::COUNTER_DISPLAY_RULE);
+
+ // These are all only for introspection and empty on all targets.
+ rules.register_empty::<crate::introspection::CounterUpdateElem>();
+ rules.register_empty::<crate::introspection::StateUpdateElem>();
+ rules.register_empty::<crate::introspection::MetadataElem>();
+ rules.register_empty::<crate::model::PrefixInfo>();
+
+ rules
+ }
+
+ /// Registers a rule for all targets.
+ fn register_empty<T: NativeElement>(&mut self) {
+ self.register_builtin::<T>(|_, _, _| Ok(Content::empty()));
+ }
+
+ /// Registers a rule for all targets.
+ fn register_builtin<T: NativeElement>(&mut self, f: ShowFn<T>) {
+ self.register(Target::Paged, f);
+ self.register(Target::Html, f);
+ }
+
+ /// Registers a rule for a target.
+ ///
+ /// Panics if a rule already exists for this target-element combination.
+ pub fn register<T: NativeElement>(&mut self, target: Target, f: ShowFn<T>) {
+ let res = self.rules.insert((T::ELEM, target), NativeShowRule::new(f));
+ if res.is_some() {
+ panic!(
+ "duplicate native show rule for `{}` on {target:?} target",
+ T::ELEM.name()
+ )
+ }
+ }
+
+ /// Retrieves the rule that applies to the `content` on the current
+ /// `target`.
+ pub fn get(&self, target: Target, content: &Content) -> Option<NativeShowRule> {
+ self.rules.get(&(content.func(), target)).copied()
+ }
+}
+
+impl Default for NativeRuleMap {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+pub use rule::NativeShowRule;
+
+mod rule {
+ use super::*;
+
+ /// The show rule for a native element.
+ #[derive(Copy, Clone)]
+ pub struct NativeShowRule {
+ /// The element to which this rule applies.
+ elem: Element,
+ /// Must only be called with content of the appropriate type.
+ f: unsafe fn(
+ elem: &Content,
+ engine: &mut Engine,
+ styles: StyleChain,
+ ) -> SourceResult<Content>,
+ }
+
+ impl NativeShowRule {
+ /// Create a new type-erased show rule.
+ pub fn new<T: NativeElement>(f: ShowFn<T>) -> Self {
+ Self {
+ elem: T::ELEM,
+ // Safety: The two function pointer types only differ in the
+ // first argument, which changes from `&Packed<T>` to
+ // `&Content`. `Packed<T>` is a transparent wrapper around
+ // `Content`. The resulting function is unsafe to call because
+ // content of the correct type must be passed to it.
+ #[allow(clippy::missing_transmute_annotations)]
+ f: unsafe { std::mem::transmute(f) },
+ }
+ }
+
+ /// Applies the rule to content. Panics if the content is of the wrong
+ /// type.
+ pub fn apply(
+ &self,
+ content: &Content,
+ engine: &mut Engine,
+ styles: StyleChain,
+ ) -> SourceResult<Content> {
+ assert_eq!(content.elem(), self.elem);
+
+ // Safety: We just checked that the element is of the correct type.
+ unsafe { (self.f)(content, engine, styles) }
+ }
+ }
+}
diff --git a/crates/typst-library/src/foundations/target.rs b/crates/typst-library/src/foundations/target.rs
index 71e7554e..ff90f1f7 100644
--- a/crates/typst-library/src/foundations/target.rs
+++ b/crates/typst-library/src/foundations/target.rs
@@ -4,7 +4,7 @@ use crate::diag::HintedStrResult;
use crate::foundations::{elem, func, Cast, Context};
/// The export target.
-#[derive(Debug, Default, Copy, Clone, PartialEq, Hash, Cast)]
+#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum Target {
/// The target that is used for paged, fully laid-out content.
#[default]