diff options
| author | Laurenz <laurmaedje@gmail.com> | 2025-07-09 10:16:36 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-09 08:16:36 +0000 |
| commit | e5e1dcd9c01341d2cd3473ac94a70223d5966086 (patch) | |
| tree | d648efad9463cb10270d55ba35210eeb1e91ee22 /crates/typst-library/src/foundations | |
| parent | 0a3c6939dd274f40672484695d909c2cc0d0d755 (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.rs | 6 | ||||
| -rw-r--r-- | crates/typst-library/src/foundations/context.rs | 17 | ||||
| -rw-r--r-- | crates/typst-library/src/foundations/styles.rs | 129 | ||||
| -rw-r--r-- | crates/typst-library/src/foundations/target.rs | 2 |
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] |
