summaryrefslogtreecommitdiff
path: root/crates/typst-library/src/foundations
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2025-07-08 10:52:43 +0200
committerGitHub <noreply@github.com>2025-07-08 08:52:43 +0000
commit0a3c6939dd274f40672484695d909c2cc0d0d755 (patch)
tree465c10338230b895fdd06c8b3491f1734e8a2932 /crates/typst-library/src/foundations
parent36ecbb2c8dccc1a31c43fee1466f1425844d8607 (diff)
Rewrite foundations of native elements (#6547)
Diffstat (limited to 'crates/typst-library/src/foundations')
-rw-r--r--crates/typst-library/src/foundations/content/element.rs (renamed from crates/typst-library/src/foundations/element.rs)216
-rw-r--r--crates/typst-library/src/foundations/content/field.rs564
-rw-r--r--crates/typst-library/src/foundations/content/mod.rs (renamed from crates/typst-library/src/foundations/content.rs)434
-rw-r--r--crates/typst-library/src/foundations/content/packed.rs147
-rw-r--r--crates/typst-library/src/foundations/content/raw.rs426
-rw-r--r--crates/typst-library/src/foundations/content/vtable.rs383
-rw-r--r--crates/typst-library/src/foundations/mod.rs2
-rw-r--r--crates/typst-library/src/foundations/scope.rs11
-rw-r--r--crates/typst-library/src/foundations/selector.rs4
-rw-r--r--crates/typst-library/src/foundations/styles.rs222
-rw-r--r--crates/typst-library/src/foundations/target.rs2
11 files changed, 1838 insertions, 573 deletions
diff --git a/crates/typst-library/src/foundations/element.rs b/crates/typst-library/src/foundations/content/element.rs
index 7ff00b9d..49b0b0f9 100644
--- a/crates/typst-library/src/foundations/element.rs
+++ b/crates/typst-library/src/foundations/content/element.rs
@@ -2,52 +2,54 @@ use std::any::TypeId;
use std::cmp::Ordering;
use std::fmt::{self, Debug};
use std::hash::Hash;
-use std::ptr::NonNull;
-use std::sync::LazyLock;
+use std::sync::OnceLock;
use ecow::EcoString;
use smallvec::SmallVec;
-#[doc(inline)]
-pub use typst_macros::elem;
use typst_utils::Static;
use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{
- cast, Args, Content, Dict, FieldAccessError, Func, ParamInfo, Repr, Scope, Selector,
- StyleChain, Styles, Value,
+ cast, Args, Content, ContentVtable, FieldAccessError, Func, ParamInfo, Repr, Scope,
+ Selector, StyleChain, Styles, Value,
};
use crate::text::{Lang, Region};
/// A document element.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
-pub struct Element(Static<NativeElementData>);
+pub struct Element(Static<ContentVtable>);
impl Element {
/// Get the element for `T`.
- pub fn of<T: NativeElement>() -> Self {
- T::elem()
+ pub const fn of<T: NativeElement>() -> Self {
+ T::ELEM
+ }
+
+ /// Get the element for `T`.
+ pub const fn from_vtable(vtable: &'static ContentVtable) -> Self {
+ Self(Static(vtable))
}
/// The element's normal name (e.g. `enum`).
pub fn name(self) -> &'static str {
- self.0.name
+ self.vtable().name
}
/// The element's title case name, for use in documentation
/// (e.g. `Numbered List`).
pub fn title(&self) -> &'static str {
- self.0.title
+ self.vtable().title
}
/// Documentation for the element (as Markdown).
pub fn docs(&self) -> &'static str {
- self.0.docs
+ self.vtable().docs
}
/// Search keywords for the element.
pub fn keywords(&self) -> &'static [&'static str] {
- self.0.keywords
+ self.vtable().keywords
}
/// Construct an instance of this element.
@@ -56,12 +58,12 @@ impl Element {
engine: &mut Engine,
args: &mut Args,
) -> SourceResult<Content> {
- (self.0.construct)(engine, args)
+ (self.vtable().construct)(engine, args)
}
/// Execute the set rule for the element and return the resulting style map.
pub fn set(self, engine: &mut Engine, mut args: Args) -> SourceResult<Styles> {
- let styles = (self.0.set)(engine, &mut args)?;
+ let styles = (self.vtable().set)(engine, &mut args)?;
args.finish()?;
Ok(styles)
}
@@ -77,12 +79,7 @@ impl Element {
/// Whether the element has the given capability where the capability is
/// given by a `TypeId`.
pub fn can_type_id(self, type_id: TypeId) -> bool {
- (self.0.vtable)(type_id).is_some()
- }
-
- /// The VTable for capabilities dispatch.
- pub fn vtable(self) -> fn(of: TypeId) -> Option<NonNull<()>> {
- self.0.vtable
+ (self.vtable().capability)(type_id).is_some()
}
/// Create a selector for this element.
@@ -98,12 +95,29 @@ impl Element {
/// The element's associated scope of sub-definition.
pub fn scope(&self) -> &'static Scope {
- &(self.0).0.scope
+ (self.vtable().store)().scope.get_or_init(|| (self.vtable().scope)())
}
/// Details about the element's fields.
pub fn params(&self) -> &'static [ParamInfo] {
- &(self.0).0.params
+ (self.vtable().store)().params.get_or_init(|| {
+ self.vtable()
+ .fields
+ .iter()
+ .filter(|field| !field.synthesized)
+ .map(|field| ParamInfo {
+ name: field.name,
+ docs: field.docs,
+ input: (field.input)(),
+ default: field.default,
+ positional: field.positional,
+ named: !field.positional,
+ variadic: field.variadic,
+ required: field.required,
+ settable: field.settable,
+ })
+ .collect()
+ })
}
/// Extract the field ID for the given field name.
@@ -111,7 +125,7 @@ impl Element {
if name == "label" {
return Some(255);
}
- (self.0.field_id)(name)
+ (self.vtable().field_id)(name)
}
/// Extract the field name for the given field ID.
@@ -119,7 +133,7 @@ impl Element {
if id == 255 {
return Some("label");
}
- (self.0.field_name)(id)
+ self.vtable().field(id).map(|data| data.name)
}
/// Extract the value of the field for the given field ID and style chain.
@@ -128,12 +142,20 @@ impl Element {
id: u8,
styles: StyleChain,
) -> Result<Value, FieldAccessError> {
- (self.0.field_from_styles)(id, styles)
+ self.vtable()
+ .field(id)
+ .and_then(|field| (field.get_from_styles)(styles))
+ .ok_or(FieldAccessError::Unknown)
}
/// The element's local name, if any.
pub fn local_name(&self, lang: Lang, region: Option<Region>) -> Option<&'static str> {
- (self.0).0.local_name.map(|f| f(lang, region))
+ self.vtable().local_name.map(|f| f(lang, region))
+ }
+
+ /// Retrieves the element's vtable for dynamic dispatch.
+ pub(super) fn vtable(&self) -> &'static ContentVtable {
+ (self.0).0
}
}
@@ -167,84 +189,34 @@ cast! {
v: Func => v.element().ok_or("expected element")?,
}
-/// A Typst element that is defined by a native Rust type.
-pub trait NativeElement:
- Debug
- + Clone
- + PartialEq
- + Hash
- + Construct
- + Set
- + Capable
- + Fields
- + Repr
- + Send
- + Sync
- + 'static
-{
- /// Get the element for the native Rust element.
- fn elem() -> Element
- where
- Self: Sized,
- {
- Element::from(Self::data())
- }
+/// Lazily initialized data for an element.
+#[derive(Default)]
+pub struct LazyElementStore {
+ pub scope: OnceLock<Scope>,
+ pub params: OnceLock<Vec<ParamInfo>>,
+}
- /// Pack the element into type-erased content.
- fn pack(self) -> Content
- where
- Self: Sized,
- {
- Content::new(self)
+impl LazyElementStore {
+ /// Create an empty store.
+ pub const fn new() -> Self {
+ Self { scope: OnceLock::new(), params: OnceLock::new() }
}
-
- /// Get the element data for the native Rust element.
- fn data() -> &'static NativeElementData
- where
- Self: Sized;
}
-/// Used to cast an element to a trait object for a trait it implements.
+/// A Typst element that is defined by a native Rust type.
///
/// # Safety
-/// If the `vtable` function returns `Some(p)`, then `p` must be a valid pointer
-/// to a vtable of `Packed<Self>` w.r.t to the trait `C` where `capability` is
-/// `TypeId::of::<dyn C>()`.
-pub unsafe trait Capable {
- /// Get the pointer to the vtable for the given capability / trait.
- fn vtable(capability: TypeId) -> Option<NonNull<()>>;
-}
-
-/// Defines how fields of an element are accessed.
-pub trait Fields {
- /// An enum with the fields of the element.
- type Enum
- where
- Self: Sized;
-
- /// Whether the element has the given field set.
- fn has(&self, id: u8) -> bool;
-
- /// Get the field with the given field ID.
- fn field(&self, id: u8) -> Result<Value, FieldAccessError>;
-
- /// Get the field with the given ID in the presence of styles.
- fn field_with_styles(
- &self,
- id: u8,
- styles: StyleChain,
- ) -> Result<Value, FieldAccessError>;
-
- /// Get the field with the given ID from the styles.
- fn field_from_styles(id: u8, styles: StyleChain) -> Result<Value, FieldAccessError>
- where
- Self: Sized;
-
- /// Resolve all fields with the styles and save them in-place.
- fn materialize(&mut self, styles: StyleChain);
+/// `ELEM` must hold the correct `Element` for `Self`.
+pub unsafe trait NativeElement:
+ Debug + Clone + Hash + Construct + Set + Send + Sync + 'static
+{
+ /// The associated element.
+ const ELEM: Element;
- /// Get the fields of the element.
- fn fields(&self) -> Dict;
+ /// Pack the element into type-erased content.
+ fn pack(self) -> Content {
+ Content::new(self)
+ }
}
/// An element's constructor function.
@@ -266,48 +238,6 @@ pub trait Set {
Self: Sized;
}
-/// Defines a native element.
-#[derive(Debug)]
-pub struct NativeElementData {
- /// The element's normal name (e.g. `align`), as exposed to Typst.
- pub name: &'static str,
- /// The element's title case name (e.g. `Align`).
- pub title: &'static str,
- /// The documentation for this element as a string.
- pub docs: &'static str,
- /// A list of alternate search terms for this element.
- pub keywords: &'static [&'static str],
- /// The constructor for this element (see [`Construct`]).
- pub construct: fn(&mut Engine, &mut Args) -> SourceResult<Content>,
- /// Executes this element's set rule (see [`Set`]).
- pub set: fn(&mut Engine, &mut Args) -> SourceResult<Styles>,
- /// Gets the vtable for one of this element's capabilities
- /// (see [`Capable`]).
- pub vtable: fn(capability: TypeId) -> Option<NonNull<()>>,
- /// Gets the numeric index of this field by its name.
- pub field_id: fn(name: &str) -> Option<u8>,
- /// Gets the name of a field by its numeric index.
- pub field_name: fn(u8) -> Option<&'static str>,
- /// Get the field with the given ID in the presence of styles (see [`Fields`]).
- pub field_from_styles: fn(u8, StyleChain) -> Result<Value, FieldAccessError>,
- /// Gets the localized name for this element (see [`LocalName`][crate::text::LocalName]).
- pub local_name: Option<fn(Lang, Option<Region>) -> &'static str>,
- pub scope: LazyLock<Scope>,
- /// A list of parameter information for each field.
- pub params: LazyLock<Vec<ParamInfo>>,
-}
-
-impl From<&'static NativeElementData> for Element {
- fn from(data: &'static NativeElementData) -> Self {
- Self(Static(data))
- }
-}
-
-cast! {
- &'static NativeElementData,
- self => Element::from(self).into_value(),
-}
-
/// Synthesize fields on an element. This happens before execution of any show
/// rule.
pub trait Synthesize {
@@ -331,3 +261,9 @@ pub trait ShowSet {
/// that should work even in the face of a user-defined show rule.
fn show_set(&self, styles: StyleChain) -> Styles;
}
+
+/// Tries to extract the plain-text representation of the element.
+pub trait PlainText {
+ /// Write this element's plain text into the given buffer.
+ fn plain_text(&self, text: &mut EcoString);
+}
diff --git a/crates/typst-library/src/foundations/content/field.rs b/crates/typst-library/src/foundations/content/field.rs
new file mode 100644
index 00000000..8d0fe529
--- /dev/null
+++ b/crates/typst-library/src/foundations/content/field.rs
@@ -0,0 +1,564 @@
+use std::fmt::{self, Debug};
+use std::hash::Hash;
+use std::marker::PhantomData;
+use std::sync::OnceLock;
+
+use ecow::{eco_format, EcoString};
+
+use crate::foundations::{
+ Container, Content, FieldVtable, Fold, FoldFn, IntoValue, NativeElement, Packed,
+ Property, Reflect, Repr, Resolve, StyleChain,
+};
+
+/// An accessor for the `I`-th field of the element `E`. Values of this type are
+/// generated for each field of an element can be used to interact with this
+/// field programmatically, for example to access the style chain, as in
+/// `styles.get(TextElem::size)`.
+#[derive(Copy, Clone)]
+pub struct Field<E: NativeElement, const I: u8>(pub PhantomData<E>);
+
+impl<E: NativeElement, const I: u8> Field<E, I> {
+ /// Creates a new zero-sized accessor.
+ pub const fn new() -> Self {
+ Self(PhantomData)
+ }
+
+ /// The index of the projected field.
+ pub const fn index(self) -> u8 {
+ I
+ }
+
+ /// Creates a dynamic property instance for this field.
+ ///
+ /// Prefer [`Content::set`] or
+ /// [`Styles::set`](crate::foundations::Styles::set) when working with
+ /// existing content or style value.
+ pub fn set(self, value: E::Type) -> Property
+ where
+ E: SettableProperty<I>,
+ E::Type: Debug + Clone + Hash + Send + Sync + 'static,
+ {
+ Property::new(self, value)
+ }
+}
+
+impl<E: NativeElement, const I: u8> Default for Field<E, I> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+/// A field that is present on every instance of the element.
+pub trait RequiredField<const I: u8>: NativeElement {
+ type Type: Clone;
+
+ const FIELD: RequiredFieldData<Self, I>;
+}
+
+/// Metadata and routines for a [`RequiredField`].
+pub struct RequiredFieldData<E: RequiredField<I>, const I: u8> {
+ name: &'static str,
+ docs: &'static str,
+ get: fn(&E) -> &E::Type,
+}
+
+impl<E: RequiredField<I>, const I: u8> RequiredFieldData<E, I> {
+ /// Creates the data from its parts. This is called in the `#[elem]` macro.
+ pub const fn new(
+ name: &'static str,
+ docs: &'static str,
+ get: fn(&E) -> &E::Type,
+ ) -> Self {
+ Self { name, docs, get }
+ }
+
+ /// Creates the vtable for a `#[required]` field.
+ pub const fn vtable() -> FieldVtable<Packed<E>>
+ where
+ E: RequiredField<I>,
+ E::Type: Reflect + IntoValue + PartialEq,
+ {
+ FieldVtable {
+ name: E::FIELD.name,
+ docs: E::FIELD.docs,
+ positional: true,
+ required: true,
+ variadic: false,
+ settable: false,
+ synthesized: false,
+ input: || <E::Type as Reflect>::input(),
+ default: None,
+ has: |_| true,
+ get: |elem| Some((E::FIELD.get)(elem).clone().into_value()),
+ get_with_styles: |elem, _| Some((E::FIELD.get)(elem).clone().into_value()),
+ get_from_styles: |_| None,
+ materialize: |_, _| {},
+ eq: |a, b| (E::FIELD.get)(a) == (E::FIELD.get)(b),
+ }
+ }
+
+ /// Creates the vtable for a `#[variadic]` field.
+ pub const fn vtable_variadic() -> FieldVtable<Packed<E>>
+ where
+ E: RequiredField<I>,
+ E::Type: Container + IntoValue + PartialEq,
+ <E::Type as Container>::Inner: Reflect,
+ {
+ FieldVtable {
+ name: E::FIELD.name,
+ docs: E::FIELD.docs,
+ positional: true,
+ required: true,
+ variadic: true,
+ settable: false,
+ synthesized: false,
+ input: || <<E::Type as Container>::Inner as Reflect>::input(),
+ default: None,
+ has: |_| true,
+ get: |elem| Some((E::FIELD.get)(elem).clone().into_value()),
+ get_with_styles: |elem, _| Some((E::FIELD.get)(elem).clone().into_value()),
+ get_from_styles: |_| None,
+ materialize: |_, _| {},
+ eq: |a, b| (E::FIELD.get)(a) == (E::FIELD.get)(b),
+ }
+ }
+}
+
+/// A field that is initially unset, but may be set through a
+/// [`Synthesize`](crate::foundations::Synthesize) implementation.
+pub trait SynthesizedField<const I: u8>: NativeElement {
+ type Type: Clone;
+
+ const FIELD: SynthesizedFieldData<Self, I>;
+}
+
+/// Metadata and routines for a [`SynthesizedField`].
+pub struct SynthesizedFieldData<E: SynthesizedField<I>, const I: u8> {
+ name: &'static str,
+ docs: &'static str,
+ get: fn(&E) -> &Option<E::Type>,
+}
+
+impl<E: SynthesizedField<I>, const I: u8> SynthesizedFieldData<E, I> {
+ /// Creates the data from its parts. This is called in the `#[elem]` macro.
+ pub const fn new(
+ name: &'static str,
+ docs: &'static str,
+ get: fn(&E) -> &Option<E::Type>,
+ ) -> Self {
+ Self { name, docs, get }
+ }
+
+ /// Creates type-erased metadata and routines for a `#[synthesized]` field.
+ pub const fn vtable() -> FieldVtable<Packed<E>>
+ where
+ E: SynthesizedField<I>,
+ E::Type: Reflect + IntoValue + PartialEq,
+ {
+ FieldVtable {
+ name: E::FIELD.name,
+ docs: E::FIELD.docs,
+ positional: false,
+ required: false,
+ variadic: false,
+ settable: false,
+ synthesized: true,
+ input: || <E::Type as Reflect>::input(),
+ default: None,
+ has: |elem| (E::FIELD.get)(elem).is_some(),
+ get: |elem| (E::FIELD.get)(elem).clone().map(|v| v.into_value()),
+ get_with_styles: |elem, _| {
+ (E::FIELD.get)(elem).clone().map(|v| v.into_value())
+ },
+ get_from_styles: |_| None,
+ materialize: |_, _| {},
+ // Synthesized fields don't affect equality.
+ eq: |_, _| true,
+ }
+ }
+}
+
+/// A field that is not actually there. It's only visible in the docs.
+pub trait ExternalField<const I: u8>: NativeElement {
+ type Type;
+
+ const FIELD: ExternalFieldData<Self, I>;
+}
+
+/// Metadata for an [`ExternalField`].
+pub struct ExternalFieldData<E: ExternalField<I>, const I: u8> {
+ name: &'static str,
+ docs: &'static str,
+ default: fn() -> E::Type,
+}
+
+impl<E: ExternalField<I>, const I: u8> ExternalFieldData<E, I> {
+ /// Creates the data from its parts. This is called in the `#[elem]` macro.
+ pub const fn new(
+ name: &'static str,
+ docs: &'static str,
+ default: fn() -> E::Type,
+ ) -> Self {
+ Self { name, docs, default }
+ }
+
+ /// Creates type-erased metadata and routines for an `#[external]` field.
+ pub const fn vtable() -> FieldVtable<Packed<E>>
+ where
+ E: ExternalField<I>,
+ E::Type: Reflect + IntoValue,
+ {
+ FieldVtable {
+ name: E::FIELD.name,
+ docs: E::FIELD.docs,
+ positional: false,
+ required: false,
+ variadic: false,
+ settable: false,
+ synthesized: false,
+ input: || <E::Type as Reflect>::input(),
+ default: Some(|| (E::FIELD.default)().into_value()),
+ has: |_| false,
+ get: |_| None,
+ get_with_styles: |_, _| None,
+ get_from_styles: |_| None,
+ materialize: |_, _| {},
+ eq: |_, _| true,
+ }
+ }
+}
+
+/// A field that has a default value and can be configured via a set rule, but
+/// can also present on elements and be present in the constructor.
+pub trait SettableField<const I: u8>: NativeElement {
+ type Type: Clone;
+
+ const FIELD: SettableFieldData<Self, I>;
+}
+
+/// Metadata and routines for a [`SettableField`].
+pub struct SettableFieldData<E: SettableField<I>, const I: u8> {
+ get: fn(&E) -> &Settable<E, I>,
+ get_mut: fn(&mut E) -> &mut Settable<E, I>,
+ property: SettablePropertyData<E, I>,
+}
+
+impl<E: SettableField<I>, const I: u8> SettableFieldData<E, I> {
+ /// Creates the data from its parts. This is called in the `#[elem]` macro.
+ pub const fn new(
+ name: &'static str,
+ docs: &'static str,
+ positional: bool,
+ get: fn(&E) -> &Settable<E, I>,
+ get_mut: fn(&mut E) -> &mut Settable<E, I>,
+ default: fn() -> E::Type,
+ slot: fn() -> &'static OnceLock<E::Type>,
+ ) -> Self {
+ Self {
+ get,
+ get_mut,
+ property: SettablePropertyData::new(name, docs, positional, default, slot),
+ }
+ }
+
+ /// Ensures that the property is folded on every access. See the
+ /// documentation of the [`Fold`] trait for more details.
+ pub const fn with_fold(mut self) -> Self
+ where
+ E::Type: Fold,
+ {
+ self.property.fold = Some(E::Type::fold);
+ self
+ }
+
+ /// Creates type-erased metadata and routines for a normal settable field.
+ pub const fn vtable() -> FieldVtable<Packed<E>>
+ where
+ E: SettableField<I>,
+ E::Type: Reflect + IntoValue + PartialEq,
+ {
+ FieldVtable {
+ name: E::FIELD.property.name,
+ docs: E::FIELD.property.docs,
+ positional: E::FIELD.property.positional,
+ required: false,
+ variadic: false,
+ settable: true,
+ synthesized: false,
+ input: || <E::Type as Reflect>::input(),
+ default: Some(|| E::default().into_value()),
+ has: |elem| (E::FIELD.get)(elem).is_set(),
+ get: |elem| (E::FIELD.get)(elem).as_option().clone().map(|v| v.into_value()),
+ get_with_styles: |elem, styles| {
+ Some((E::FIELD.get)(elem).get_cloned(styles).into_value())
+ },
+ get_from_styles: |styles| {
+ Some(styles.get_cloned::<E, I>(Field::new()).into_value())
+ },
+ materialize: |elem, styles| {
+ if !(E::FIELD.get)(elem).is_set() {
+ (E::FIELD.get_mut)(elem).set(styles.get_cloned::<E, I>(Field::new()));
+ }
+ },
+ eq: |a, b| (E::FIELD.get)(a).as_option() == (E::FIELD.get)(b).as_option(),
+ }
+ }
+}
+
+/// A field that has a default value and can be configured via a set rule, but
+/// is never present on elements.
+///
+/// This is provided for all `SettableField` impls through a blanket impl. In
+/// the case of `#[ghost]` fields, which only live in the style chain and not in
+/// elements, it is also implemented manually.
+pub trait SettableProperty<const I: u8>: NativeElement {
+ type Type: Clone;
+
+ const FIELD: SettablePropertyData<Self, I>;
+ const FOLD: Option<FoldFn<Self::Type>> = Self::FIELD.fold;
+
+ /// Produces an instance of the property's default value.
+ fn default() -> Self::Type {
+ // Avoid recreating an expensive instance over and over, but also
+ // avoid unnecessary lazy initialization for cheap types.
+ if std::mem::needs_drop::<Self::Type>() {
+ Self::default_ref().clone()
+ } else {
+ (Self::FIELD.default)()
+ }
+ }
+
+ /// Produces a static reference to this property's default value.
+ fn default_ref() -> &'static Self::Type {
+ (Self::FIELD.slot)().get_or_init(Self::FIELD.default)
+ }
+}
+
+impl<T, const I: u8> SettableProperty<I> for T
+where
+ T: SettableField<I>,
+{
+ type Type = <Self as SettableField<I>>::Type;
+
+ const FIELD: SettablePropertyData<Self, I> =
+ <Self as SettableField<I>>::FIELD.property;
+}
+
+/// Metadata and routines for a [`SettableProperty`].
+pub struct SettablePropertyData<E: SettableProperty<I>, const I: u8> {
+ name: &'static str,
+ docs: &'static str,
+ positional: bool,
+ default: fn() -> E::Type,
+ slot: fn() -> &'static OnceLock<E::Type>,
+ fold: Option<FoldFn<E::Type>>,
+}
+
+impl<E: SettableProperty<I>, const I: u8> SettablePropertyData<E, I> {
+ /// Creates the data from its parts. This is called in the `#[elem]` macro.
+ pub const fn new(
+ name: &'static str,
+ docs: &'static str,
+ positional: bool,
+ default: fn() -> E::Type,
+ slot: fn() -> &'static OnceLock<E::Type>,
+ ) -> Self {
+ Self { name, docs, positional, default, slot, fold: None }
+ }
+
+ /// Ensures that the property is folded on every access. See the
+ /// documentation of the [`Fold`] trait for more details.
+ pub const fn with_fold(self) -> Self
+ where
+ E::Type: Fold,
+ {
+ Self { fold: Some(E::Type::fold), ..self }
+ }
+
+ /// Creates type-erased metadata and routines for a `#[ghost]` field.
+ pub const fn vtable() -> FieldVtable<Packed<E>>
+ where
+ E: SettableProperty<I>,
+ E::Type: Reflect + IntoValue + PartialEq,
+ {
+ FieldVtable {
+ name: E::FIELD.name,
+ docs: E::FIELD.docs,
+ positional: E::FIELD.positional,
+ required: false,
+ variadic: false,
+ settable: true,
+ synthesized: false,
+ input: || <E::Type as Reflect>::input(),
+ default: Some(|| E::default().into_value()),
+ has: |_| false,
+ get: |_| None,
+ get_with_styles: |_, styles| {
+ Some(styles.get_cloned::<E, I>(Field::new()).into_value())
+ },
+ get_from_styles: |styles| {
+ Some(styles.get_cloned::<E, I>(Field::new()).into_value())
+ },
+ materialize: |_, _| {},
+ eq: |_, _| true,
+ }
+ }
+}
+
+/// A settable property that can be accessed by reference (because it is not
+/// folded).
+pub trait RefableProperty<const I: u8>: SettableProperty<I> {}
+
+/// A settable field of an element.
+///
+/// The field can be in two states: Unset or present.
+///
+/// See [`StyleChain`] for more details about the available accessor methods.
+#[derive(Copy, Clone, Hash)]
+pub struct Settable<E: NativeElement, const I: u8>(Option<E::Type>)
+where
+ E: SettableProperty<I>;
+
+impl<E: NativeElement, const I: u8> Settable<E, I>
+where
+ E: SettableProperty<I>,
+{
+ /// Creates a new unset instance.
+ pub fn new() -> Self {
+ Self(None)
+ }
+
+ /// Sets the instance to a value.
+ pub fn set(&mut self, value: E::Type) {
+ self.0 = Some(value);
+ }
+
+ /// Clears the value from the instance.
+ pub fn unset(&mut self) {
+ self.0 = None;
+ }
+
+ /// Views the type as an [`Option`] which is `Some` if the type is set
+ /// and `None` if it is unset.
+ pub fn as_option(&self) -> &Option<E::Type> {
+ &self.0
+ }
+
+ /// Views the type as a mutable [`Option`].
+ pub fn as_option_mut(&mut self) -> &mut Option<E::Type> {
+ &mut self.0
+ }
+
+ /// Whether the field is set.
+ pub fn is_set(&self) -> bool {
+ self.0.is_some()
+ }
+
+ /// Retrieves the value given styles. The styles are used if the value is
+ /// unset.
+ pub fn get<'a>(&'a self, styles: StyleChain<'a>) -> E::Type
+ where
+ E::Type: Copy,
+ {
+ self.get_cloned(styles)
+ }
+
+ /// Retrieves and clones the value given styles. The styles are used if the
+ /// value is unset or if it needs folding.
+ pub fn get_cloned<'a>(&'a self, styles: StyleChain<'a>) -> E::Type {
+ if let Some(fold) = E::FOLD {
+ let mut res = styles.get_cloned::<E, I>(Field::new());
+ if let Some(value) = &self.0 {
+ res = fold(value.clone(), res);
+ }
+ res
+ } else if let Some(value) = &self.0 {
+ value.clone()
+ } else {
+ styles.get_cloned::<E, I>(Field::new())
+ }
+ }
+
+ /// Retrieves a reference to the value given styles. The styles are used if
+ /// the value is unset.
+ pub fn get_ref<'a>(&'a self, styles: StyleChain<'a>) -> &'a E::Type
+ where
+ E: RefableProperty<I>,
+ {
+ if let Some(value) = &self.0 {
+ value
+ } else {
+ styles.get_ref::<E, I>(Field::new())
+ }
+ }
+
+ /// Retrieves the value and then immediately [resolves](Resolve) it.
+ pub fn resolve<'a>(&'a self, styles: StyleChain<'a>) -> <E::Type as Resolve>::Output
+ where
+ E::Type: Resolve,
+ {
+ self.get_cloned(styles).resolve(styles)
+ }
+}
+
+impl<E: NativeElement, const I: u8> Debug for Settable<E, I>
+where
+ E: SettableProperty<I>,
+ E::Type: Debug,
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl<E: NativeElement, const I: u8> Default for Settable<E, I>
+where
+ E: SettableProperty<I>,
+{
+ fn default() -> Self {
+ Self(None)
+ }
+}
+
+impl<E: NativeElement, const I: u8> From<Option<E::Type>> for Settable<E, I>
+where
+ E: SettableProperty<I>,
+{
+ fn from(value: Option<E::Type>) -> Self {
+ Self(value)
+ }
+}
+
+/// An error arising when trying to access a field of content.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum FieldAccessError {
+ Unknown,
+ Unset,
+}
+
+impl FieldAccessError {
+ /// Formats the error message given the content and the field name.
+ #[cold]
+ pub fn message(self, content: &Content, field: &str) -> EcoString {
+ let elem_name = content.elem().name();
+ match self {
+ FieldAccessError::Unknown => {
+ eco_format!("{elem_name} does not have field {}", field.repr())
+ }
+ FieldAccessError::Unset => {
+ eco_format!(
+ "field {} in {elem_name} is not known at this point",
+ field.repr()
+ )
+ }
+ }
+ }
+
+ /// Formats the error message for an `at` calls without a default value.
+ #[cold]
+ pub fn message_no_default(self, content: &Content, field: &str) -> EcoString {
+ let mut msg = self.message(content, field);
+ msg.push_str(" and no default was specified");
+ msg
+ }
+}
diff --git a/crates/typst-library/src/foundations/content.rs b/crates/typst-library/src/foundations/content/mod.rs
index 1855bb70..7ba790d8 100644
--- a/crates/typst-library/src/foundations/content.rs
+++ b/crates/typst-library/src/foundations/content/mod.rs
@@ -1,23 +1,33 @@
-use std::any::TypeId;
+mod element;
+mod field;
+mod packed;
+mod raw;
+mod vtable;
+
+pub use self::element::*;
+pub use self::field::*;
+pub use self::packed::Packed;
+pub use self::vtable::{ContentVtable, FieldVtable};
+#[doc(inline)]
+pub use typst_macros::elem;
+
use std::fmt::{self, Debug, Formatter};
-use std::hash::{Hash, Hasher};
+use std::hash::Hash;
use std::iter::{self, Sum};
-use std::marker::PhantomData;
-use std::ops::{Add, AddAssign, ControlFlow, Deref, DerefMut};
-use std::sync::Arc;
+use std::ops::{Add, AddAssign, ControlFlow};
use comemo::Tracked;
use ecow::{eco_format, EcoString};
use serde::{Serialize, Serializer};
+
use typst_syntax::Span;
-use typst_utils::{fat, singleton, LazyHash, SmallBitSet};
+use typst_utils::singleton;
use crate::diag::{SourceResult, StrResult};
use crate::engine::Engine;
use crate::foundations::{
- elem, func, scope, ty, Context, Dict, Element, Fields, IntoValue, Label,
- NativeElement, Recipe, RecipeIndex, Repr, Selector, Str, Style, StyleChain, Styles,
- Value,
+ func, repr, scope, ty, Context, Dict, IntoValue, Label, Property, Recipe,
+ RecipeIndex, Repr, Selector, Str, Style, StyleChain, Styles, Value,
};
use crate::introspection::Location;
use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides};
@@ -68,43 +78,14 @@ use crate::text::UnderlineElem;
/// elements the content is composed of and what fields they have.
/// Alternatively, you can inspect the output of the [`repr`] function.
#[ty(scope, cast)]
-#[derive(Clone, Hash)]
-#[allow(clippy::derived_hash_with_manual_eq)]
-pub struct Content {
- /// The partially element-dependent inner data.
- inner: Arc<Inner<dyn Bounds>>,
- /// The element's source code location.
- span: Span,
-}
-
-/// The inner representation behind the `Arc`.
-#[derive(Hash)]
-struct Inner<T: ?Sized + 'static> {
- /// An optional label attached to the element.
- label: Option<Label>,
- /// The element's location which identifies it in the layouted output.
- location: Option<Location>,
- /// Manages the element during realization.
- /// - If bit 0 is set, the element is prepared.
- /// - If bit n is set, the element is guarded against the n-th show rule
- /// recipe from the top of the style chain (counting from 1).
- lifecycle: SmallBitSet,
- /// The element's raw data.
- elem: LazyHash<T>,
-}
+#[derive(Clone, PartialEq, Hash)]
+#[repr(transparent)]
+pub struct Content(raw::RawContent);
impl Content {
/// Creates a new content from an element.
pub fn new<T: NativeElement>(elem: T) -> Self {
- Self {
- inner: Arc::new(Inner {
- label: None,
- location: None,
- lifecycle: SmallBitSet::new(),
- elem: elem.into(),
- }),
- span: Span::detached(),
- }
+ Self(raw::RawContent::new(elem))
}
/// Creates a empty sequence content.
@@ -114,25 +95,25 @@ impl Content {
/// Get the element of this content.
pub fn elem(&self) -> Element {
- self.inner.elem.dyn_elem()
+ self.0.elem()
}
/// Get the span of the content.
pub fn span(&self) -> Span {
- self.span
+ self.0.span()
}
/// Set the span of the content.
pub fn spanned(mut self, span: Span) -> Self {
- if self.span.is_detached() {
- self.span = span;
+ if self.0.span().is_detached() {
+ *self.0.span_mut() = span;
}
self
}
/// Get the label of the content.
pub fn label(&self) -> Option<Label> {
- self.inner.label
+ self.0.meta().label
}
/// Attach a label to the content.
@@ -143,7 +124,7 @@ impl Content {
/// Set the label of the content.
pub fn set_label(&mut self, label: Label) {
- self.make_mut().label = Some(label);
+ self.0.meta_mut().label = Some(label);
}
/// Assigns a location to the content.
@@ -159,28 +140,28 @@ impl Content {
/// Set the location of the content.
pub fn set_location(&mut self, location: Location) {
- self.make_mut().location = Some(location);
+ self.0.meta_mut().location = Some(location);
}
/// Check whether a show rule recipe is disabled.
pub fn is_guarded(&self, index: RecipeIndex) -> bool {
- self.inner.lifecycle.contains(index.0)
+ self.0.meta().lifecycle.contains(index.0)
}
/// Disable a show rule recipe.
pub fn guarded(mut self, index: RecipeIndex) -> Self {
- self.make_mut().lifecycle.insert(index.0);
+ self.0.meta_mut().lifecycle.insert(index.0);
self
}
/// Whether this content has already been prepared.
pub fn is_prepared(&self) -> bool {
- self.inner.lifecycle.contains(0)
+ self.0.meta().lifecycle.contains(0)
}
/// Mark this content as prepared.
pub fn mark_prepared(&mut self) {
- self.make_mut().lifecycle.insert(0);
+ self.0.meta_mut().lifecycle.insert(0);
}
/// Get a field by ID.
@@ -198,9 +179,14 @@ impl Content {
return Ok(label.into_value());
}
}
- match styles {
- Some(styles) => self.inner.elem.field_with_styles(id, styles),
- None => self.inner.elem.field(id),
+
+ match self.0.handle().field(id) {
+ Some(handle) => match styles {
+ Some(styles) => handle.get_with_styles(styles),
+ None => handle.get(),
+ }
+ .ok_or(FieldAccessError::Unset),
+ None => Err(FieldAccessError::Unknown),
}
}
@@ -215,8 +201,11 @@ impl Content {
.map(|label| label.into_value())
.ok_or(FieldAccessError::Unknown);
}
- let id = self.elem().field_id(name).ok_or(FieldAccessError::Unknown)?;
- self.get(id, None)
+
+ match self.elem().field_id(name).and_then(|id| self.0.handle().field(id)) {
+ Some(handle) => handle.get().ok_or(FieldAccessError::Unset),
+ None => Err(FieldAccessError::Unknown),
+ }
}
/// Get a field by ID, returning a missing field error if it does not exist.
@@ -240,7 +229,9 @@ impl Content {
/// Resolve all fields with the styles and save them in-place.
pub fn materialize(&mut self, styles: StyleChain) {
- self.make_mut().elem.materialize(styles);
+ for id in 0..self.elem().vtable().fields.len() as u8 {
+ self.0.handle_mut().field(id).unwrap().materialize(styles);
+ }
}
/// Create a new sequence element from multiples elements.
@@ -257,7 +248,7 @@ impl Content {
/// Whether the contained element is of type `T`.
pub fn is<T: NativeElement>(&self) -> bool {
- self.inner.elem.dyn_type_id() == TypeId::of::<T>()
+ self.0.is::<T>()
}
/// Downcasts the element to a packed value.
@@ -280,16 +271,6 @@ impl Content {
self.into_packed::<T>().map(Packed::unpack)
}
- /// Makes sure the content is not shared and returns a mutable reference to
- /// the inner data.
- fn make_mut(&mut self) -> &mut Inner<dyn Bounds> {
- let arc = &mut self.inner;
- if Arc::strong_count(arc) > 1 || Arc::weak_count(arc) > 0 {
- *self = arc.elem.dyn_clone(arc, self.span);
- }
- Arc::get_mut(&mut self.inner).unwrap()
- }
-
/// Whether the contained element has the given capability.
pub fn can<C>(&self) -> bool
where
@@ -304,13 +285,7 @@ impl Content {
where
C: ?Sized + 'static,
{
- // Safety: The vtable comes from the `Capable` implementation which
- // guarantees to return a matching vtable for `Packed<T>` and `C`.
- // Since any `Packed<T>` is a repr(transparent) `Content`, we can also
- // use a `*const Content` pointer.
- let vtable = self.elem().vtable()(TypeId::of::<C>())?;
- let data = self as *const Content as *const ();
- Some(unsafe { &*fat::from_raw_parts(data, vtable.as_ptr()) })
+ self.0.with::<C>()
}
/// Cast to a mutable trait object if the contained element has the given
@@ -319,18 +294,7 @@ impl Content {
where
C: ?Sized + 'static,
{
- // Safety: The vtable comes from the `Capable` implementation which
- // guarantees to return a matching vtable for `Packed<T>` and `C`.
- // Since any `Packed<T>` is a repr(transparent) `Content`, we can also
- // use a `*const Content` pointer.
- //
- // The resulting trait object contains an `&mut Packed<T>`. We do _not_
- // need to ensure that we hold the only reference to the `Arc` here
- // because `Packed<T>`'s DerefMut impl will take care of that if
- // mutable access is required.
- let vtable = self.elem().vtable()(TypeId::of::<C>())?;
- let data = self as *mut Content as *mut ();
- Some(unsafe { &mut *fat::from_raw_parts_mut(data, vtable.as_ptr()) })
+ self.0.with_mut::<C>()
}
/// Whether the content is an empty sequence.
@@ -372,6 +336,15 @@ impl Content {
Self::sequence(std::iter::repeat_with(|| self.clone()).take(count))
}
+ /// Sets a style property on the content.
+ pub fn set<E, const I: u8>(self, field: Field<E, I>, value: E::Type) -> Self
+ where
+ E: SettableProperty<I>,
+ E::Type: Debug + Clone + Hash + Send + Sync + 'static,
+ {
+ self.styled(Property::new(field, value))
+ }
+
/// Style this content with a style entry.
pub fn styled(mut self, style: impl Into<Style>) -> Self {
if let Some(style_elem) = self.to_packed_mut::<StyledElem>() {
@@ -476,7 +449,7 @@ impl Content {
// Call f on the element itself before recursively iterating its fields.
f(self.clone())?;
- for (_, value) in self.inner.elem.fields() {
+ for (_, value) in self.fields() {
walk_value(value, f)?;
}
ControlFlow::Continue(())
@@ -504,12 +477,12 @@ impl Content {
/// Link the content somewhere.
pub fn linked(self, dest: Destination) -> Self {
- self.styled(LinkElem::set_current(Some(dest)))
+ self.set(LinkElem::current, Some(dest))
}
/// Set alignments for this content.
pub fn aligned(self, align: Alignment) -> Self {
- self.styled(AlignElem::set_alignment(align))
+ self.set(AlignElem::alignment, align)
}
/// Pad this content at the sides.
@@ -562,7 +535,10 @@ impl Content {
return false;
};
- self.inner.elem.has(id)
+ match self.0.handle().field(id) {
+ Some(field) => field.has(),
+ None => false,
+ }
}
/// Access the specified field on the content. Returns the default value if
@@ -592,7 +568,12 @@ impl Content {
/// ```
#[func]
pub fn fields(&self) -> Dict {
- let mut dict = self.inner.elem.fields();
+ let mut dict = Dict::new();
+ for field in self.0.handle().fields() {
+ if let Some(value) = field.get() {
+ dict.insert(field.name.into(), value);
+ }
+ }
if let Some(label) = self.label() {
dict.insert("label".into(), label.into_value());
}
@@ -605,7 +586,7 @@ impl Content {
/// used with [counters]($counter), [state] and [queries]($query).
#[func]
pub fn location(&self) -> Option<Location> {
- self.inner.location
+ self.0.meta().location
}
}
@@ -617,7 +598,7 @@ impl Default for Content {
impl Debug for Content {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- self.inner.elem.fmt(f)
+ self.0.fmt(f)
}
}
@@ -627,16 +608,22 @@ impl<T: NativeElement> From<T> for Content {
}
}
-impl PartialEq for Content {
- fn eq(&self, other: &Self) -> bool {
- // Additional short circuit for different elements.
- self.elem() == other.elem() && self.inner.elem.dyn_eq(other)
- }
-}
-
impl Repr for Content {
fn repr(&self) -> EcoString {
- self.inner.elem.repr()
+ self.0.handle().repr().unwrap_or_else(|| {
+ let fields = self
+ .0
+ .handle()
+ .fields()
+ .filter_map(|field| field.get().map(|v| (field.name, v.repr())))
+ .map(|(name, value)| eco_format!("{name}: {value}"))
+ .collect::<Vec<_>>();
+ eco_format!(
+ "{}{}",
+ self.elem().name(),
+ repr::pretty_array_like(&fields, false),
+ )
+ })
}
}
@@ -717,190 +704,8 @@ impl Serialize for Content {
}
}
-/// The trait that combines all the other traits into a trait object.
-trait Bounds: Debug + Repr + Fields + Send + Sync + 'static {
- fn dyn_type_id(&self) -> TypeId;
- fn dyn_elem(&self) -> Element;
- fn dyn_clone(&self, inner: &Inner<dyn Bounds>, span: Span) -> Content;
- fn dyn_hash(&self, hasher: &mut dyn Hasher);
- fn dyn_eq(&self, other: &Content) -> bool;
-}
-
-impl<T: NativeElement> Bounds for T {
- fn dyn_type_id(&self) -> TypeId {
- TypeId::of::<Self>()
- }
-
- fn dyn_elem(&self) -> Element {
- Self::elem()
- }
-
- fn dyn_clone(&self, inner: &Inner<dyn Bounds>, span: Span) -> Content {
- Content {
- inner: Arc::new(Inner {
- label: inner.label,
- location: inner.location,
- lifecycle: inner.lifecycle.clone(),
- elem: LazyHash::reuse(self.clone(), &inner.elem),
- }),
- span,
- }
- }
-
- fn dyn_hash(&self, mut state: &mut dyn Hasher) {
- TypeId::of::<Self>().hash(&mut state);
- self.hash(&mut state);
- }
-
- fn dyn_eq(&self, other: &Content) -> bool {
- let Some(other) = other.to_packed::<Self>() else {
- return false;
- };
- *self == **other
- }
-}
-
-impl Hash for dyn Bounds {
- fn hash<H: Hasher>(&self, state: &mut H) {
- self.dyn_hash(state);
- }
-}
-
-/// A packed element of a static type.
-#[derive(Clone, PartialEq, Hash)]
-#[repr(transparent)]
-pub struct Packed<T: NativeElement>(
- /// Invariant: Must be of type `T`.
- Content,
- PhantomData<T>,
-);
-
-impl<T: NativeElement> Packed<T> {
- /// Pack element while retaining its static type.
- pub fn new(element: T) -> Self {
- // Safety: The element is known to be of type `T`.
- Packed(element.pack(), PhantomData)
- }
-
- /// Try to cast type-erased content into a statically known packed element.
- pub fn from_ref(content: &Content) -> Option<&Self> {
- if content.is::<T>() {
- // Safety:
- // - We have checked the type.
- // - Packed<T> is repr(transparent).
- return Some(unsafe { std::mem::transmute::<&Content, &Packed<T>>(content) });
- }
- None
- }
-
- /// Try to cast type-erased content into a statically known packed element.
- pub fn from_mut(content: &mut Content) -> Option<&mut Self> {
- if content.is::<T>() {
- // Safety:
- // - We have checked the type.
- // - Packed<T> is repr(transparent).
- return Some(unsafe {
- std::mem::transmute::<&mut Content, &mut Packed<T>>(content)
- });
- }
- None
- }
-
- /// Try to cast type-erased content into a statically known packed element.
- pub fn from_owned(content: Content) -> Result<Self, Content> {
- if content.is::<T>() {
- // Safety:
- // - We have checked the type.
- // - Packed<T> is repr(transparent).
- return Ok(unsafe { std::mem::transmute::<Content, Packed<T>>(content) });
- }
- Err(content)
- }
-
- /// Pack back into content.
- pub fn pack(self) -> Content {
- self.0
- }
-
- /// Extract the raw underlying element.
- pub fn unpack(self) -> T {
- // This function doesn't yet need owned self, but might in the future.
- (*self).clone()
- }
-
- /// The element's span.
- pub fn span(&self) -> Span {
- self.0.span()
- }
-
- /// Set the span of the element.
- pub fn spanned(self, span: Span) -> Self {
- Self(self.0.spanned(span), PhantomData)
- }
-
- /// Accesses the label of the element.
- pub fn label(&self) -> Option<Label> {
- self.0.label()
- }
-
- /// Accesses the location of the element.
- pub fn location(&self) -> Option<Location> {
- self.0.location()
- }
-
- /// Sets the location of the element.
- pub fn set_location(&mut self, location: Location) {
- self.0.set_location(location);
- }
-}
-
-impl<T: NativeElement> AsRef<T> for Packed<T> {
- fn as_ref(&self) -> &T {
- self
- }
-}
-
-impl<T: NativeElement> AsMut<T> for Packed<T> {
- fn as_mut(&mut self) -> &mut T {
- self
- }
-}
-
-impl<T: NativeElement> Deref for Packed<T> {
- type Target = T;
-
- fn deref(&self) -> &Self::Target {
- // Safety:
- // - Packed<T> guarantees that the content trait object wraps
- // an element of type `T`.
- // - This downcast works the same way as dyn Any's does. We can't reuse
- // that one because we don't want to pay the cost for every deref.
- let elem = &*self.0.inner.elem;
- unsafe { &*(elem as *const dyn Bounds as *const T) }
- }
-}
-
-impl<T: NativeElement> DerefMut for Packed<T> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- // Safety:
- // - Packed<T> guarantees that the content trait object wraps
- // an element of type `T`.
- // - We have guaranteed unique access thanks to `make_mut`.
- // - This downcast works the same way as dyn Any's does. We can't reuse
- // that one because we don't want to pay the cost for every deref.
- let elem = &mut *self.0.make_mut().elem;
- unsafe { &mut *(elem as *mut dyn Bounds as *mut T) }
- }
-}
-
-impl<T: NativeElement + Debug> Debug for Packed<T> {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- self.0.fmt(f)
- }
-}
-
/// A sequence of content.
-#[elem(Debug, Repr, PartialEq)]
+#[elem(Debug, Repr)]
pub struct SequenceElem {
/// The elements.
#[required]
@@ -922,19 +727,13 @@ impl Default for SequenceElem {
}
}
-impl PartialEq for SequenceElem {
- fn eq(&self, other: &Self) -> bool {
- self.children.iter().eq(other.children.iter())
- }
-}
-
impl Repr for SequenceElem {
fn repr(&self) -> EcoString {
if self.children.is_empty() {
"[]".into()
} else {
let elements = crate::foundations::repr::pretty_array_like(
- &self.children.iter().map(|c| c.inner.elem.repr()).collect::<Vec<_>>(),
+ &self.children.iter().map(|c| c.repr()).collect::<Vec<_>>(),
false,
);
eco_format!("sequence{}", elements)
@@ -974,49 +773,8 @@ impl Repr for StyledElem {
}
}
-/// Tries to extract the plain-text representation of the element.
-pub trait PlainText {
- /// Write this element's plain text into the given buffer.
- fn plain_text(&self, text: &mut EcoString);
-}
-
-/// An error arising when trying to access a field of content.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum FieldAccessError {
- Unknown,
- Unset,
- Internal,
-}
-
-impl FieldAccessError {
- /// Formats the error message given the content and the field name.
- #[cold]
- pub fn message(self, content: &Content, field: &str) -> EcoString {
- let elem_name = content.elem().name();
- match self {
- FieldAccessError::Unknown => {
- eco_format!("{elem_name} does not have field {}", field.repr())
- }
- FieldAccessError::Unset => {
- eco_format!(
- "field {} in {elem_name} is not known at this point",
- field.repr()
- )
- }
- FieldAccessError::Internal => {
- eco_format!(
- "internal error when accessing field {} in {elem_name} – this is a bug",
- field.repr()
- )
- }
- }
- }
-
- /// Formats the error message for an `at` calls without a default value.
- #[cold]
- pub fn message_no_default(self, content: &Content, field: &str) -> EcoString {
- let mut msg = self.message(content, field);
- msg.push_str(" and no default was specified");
- msg
+impl<T: NativeElement> IntoValue for T {
+ fn into_value(self) -> Value {
+ Value::Content(self.pack())
}
}
diff --git a/crates/typst-library/src/foundations/content/packed.rs b/crates/typst-library/src/foundations/content/packed.rs
new file mode 100644
index 00000000..71bb66a9
--- /dev/null
+++ b/crates/typst-library/src/foundations/content/packed.rs
@@ -0,0 +1,147 @@
+use std::fmt::{self, Debug, Formatter};
+use std::hash::{Hash, Hasher};
+use std::marker::PhantomData;
+use std::ops::{Deref, DerefMut};
+
+use typst_syntax::Span;
+
+use crate::foundations::{Content, Label, NativeElement};
+use crate::introspection::Location;
+
+/// A packed element of a static type.
+#[derive(Clone)]
+#[repr(transparent)]
+pub struct Packed<T: NativeElement>(
+ /// Invariant: Must be of type `T`.
+ Content,
+ PhantomData<T>,
+);
+
+impl<T: NativeElement> Packed<T> {
+ /// Pack element while retaining its static type.
+ pub fn new(element: T) -> Self {
+ // Safety: The element is known to be of type `T`.
+ Packed(element.pack(), PhantomData)
+ }
+
+ /// Try to cast type-erased content into a statically known packed element.
+ pub fn from_ref(content: &Content) -> Option<&Self> {
+ if content.is::<T>() {
+ // Safety:
+ // - We have checked the type.
+ // - Packed<T> is repr(transparent).
+ return Some(unsafe { std::mem::transmute::<&Content, &Packed<T>>(content) });
+ }
+ None
+ }
+
+ /// Try to cast type-erased content into a statically known packed element.
+ pub fn from_mut(content: &mut Content) -> Option<&mut Self> {
+ if content.is::<T>() {
+ // Safety:
+ // - We have checked the type.
+ // - Packed<T> is repr(transparent).
+ return Some(unsafe {
+ std::mem::transmute::<&mut Content, &mut Packed<T>>(content)
+ });
+ }
+ None
+ }
+
+ /// Try to cast type-erased content into a statically known packed element.
+ pub fn from_owned(content: Content) -> Result<Self, Content> {
+ if content.is::<T>() {
+ // Safety:
+ // - We have checked the type.
+ // - Packed<T> is repr(transparent).
+ return Ok(unsafe { std::mem::transmute::<Content, Packed<T>>(content) });
+ }
+ Err(content)
+ }
+
+ /// Pack back into content.
+ pub fn pack(self) -> Content {
+ self.0
+ }
+
+ /// Extract the raw underlying element.
+ pub fn unpack(self) -> T {
+ // This function doesn't yet need owned self, but might in the future.
+ (*self).clone()
+ }
+
+ /// The element's span.
+ pub fn span(&self) -> Span {
+ self.0.span()
+ }
+
+ /// Set the span of the element.
+ pub fn spanned(self, span: Span) -> Self {
+ Self(self.0.spanned(span), PhantomData)
+ }
+
+ /// Accesses the label of the element.
+ pub fn label(&self) -> Option<Label> {
+ self.0.label()
+ }
+
+ /// Accesses the location of the element.
+ pub fn location(&self) -> Option<Location> {
+ self.0.location()
+ }
+
+ /// Sets the location of the element.
+ pub fn set_location(&mut self, location: Location) {
+ self.0.set_location(location);
+ }
+
+ pub fn as_content(&self) -> &Content {
+ &self.0
+ }
+}
+
+impl<T: NativeElement> AsRef<T> for Packed<T> {
+ fn as_ref(&self) -> &T {
+ self
+ }
+}
+
+impl<T: NativeElement> AsMut<T> for Packed<T> {
+ fn as_mut(&mut self) -> &mut T {
+ self
+ }
+}
+
+impl<T: NativeElement> Deref for Packed<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ // Safety: Packed<T> guarantees that the content is of element type `T`.
+ unsafe { (self.0).0.data::<T>() }
+ }
+}
+
+impl<T: NativeElement> DerefMut for Packed<T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ // Safety: Packed<T> guarantees that the content is of element type `T`.
+ unsafe { (self.0).0.data_mut::<T>() }
+ }
+}
+
+impl<T: NativeElement + Debug> Debug for Packed<T> {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl<T: NativeElement> PartialEq for Packed<T> {
+ fn eq(&self, other: &Self) -> bool {
+ self.0 == other.0
+ }
+}
+
+impl<T: NativeElement> Hash for Packed<T> {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.0.hash(state);
+ }
+}
diff --git a/crates/typst-library/src/foundations/content/raw.rs b/crates/typst-library/src/foundations/content/raw.rs
new file mode 100644
index 00000000..f5dfffd7
--- /dev/null
+++ b/crates/typst-library/src/foundations/content/raw.rs
@@ -0,0 +1,426 @@
+use std::any::TypeId;
+use std::fmt::{self, Debug, Formatter};
+use std::hash::{Hash, Hasher};
+use std::ptr::NonNull;
+use std::sync::atomic::{self, AtomicUsize, Ordering};
+
+use typst_syntax::Span;
+use typst_utils::{fat, HashLock, SmallBitSet};
+
+use super::vtable;
+use crate::foundations::{Element, Label, NativeElement, Packed};
+use crate::introspection::Location;
+
+/// The raw, low-level implementation of content.
+///
+/// The `ptr` + `elem` fields implement a fat pointer setup similar to an
+/// `Arc<Inner<dyn Trait>>`, but in a manual way, allowing us to have a custom
+/// [vtable].
+pub struct RawContent {
+ /// A type-erased pointer to an allocation containing two things:
+ /// - A header that is the same for all elements
+ /// - Element-specific `data` that holds the specific element
+ ///
+ /// This pointer is valid for both a `Header` and an `Inner<E>` where
+ /// `E::ELEM == self.elem` and can be freely cast between both. This is
+ /// possible because
+ /// - `Inner<E>` is `repr(C)`
+ /// - The first field of `Inner<E>` is `Header`
+ /// - ISO/IEC 9899:TC2 C standard § 6.7.2.1 - 13 states that a pointer to a
+ /// structure "points to its initial member" with no padding at the start
+ ptr: NonNull<Header>,
+ /// Describes which kind of element this content holds. This is used for
+ ///
+ /// - Direct comparisons, e.g. `is::<HeadingElem>()`
+ /// - Behavior: An `Element` is just a pointer to a `ContentVtable`
+ /// containing not just data, but also function pointers for various
+ /// element-specific operations that can be performed
+ ///
+ /// It is absolutely crucial that `elem == <E as NativeElement>::ELEM` for
+ /// `Inner<E>` pointed to by `ptr`. Otherwise, things will go very wrong
+ /// since we'd be using the wrong vtable.
+ elem: Element,
+ /// The content's span.
+ span: Span,
+}
+
+/// The allocated part of an element's representation.
+///
+/// This is `repr(C)` to ensure that a pointer to the whole structure may be
+/// cast to a pointer to its first field.
+#[repr(C)]
+struct Inner<E> {
+ /// It is crucial that this is the first field because we cast between
+ /// pointers to `Inner<E>` and pointers to `Header`. See the documentation
+ /// of `RawContent::ptr` for more details.
+ header: Header,
+ /// The element struct. E.g. `E = HeadingElem`.
+ data: E,
+}
+
+/// The header that is shared by all elements.
+struct Header {
+ /// The element's reference count. This works just like for `Arc`.
+ /// Unfortunately, we have to reimplement reference counting because we
+ /// have a custom fat pointer and `Arc` wouldn't know how to drop its
+ /// contents. Something with `ManuallyDrop<Arc<_>>` might also work, but at
+ /// that point we're not gaining much and with the way it's implemented now
+ /// we can also skip the unnecessary weak reference count.
+ refs: AtomicUsize,
+ /// Metadata for the element.
+ meta: Meta,
+ /// A cell for memoizing the hash of just the `data` part of the content.
+ hash: HashLock,
+}
+
+/// Metadata that elements can hold.
+#[derive(Clone, Hash)]
+pub(super) struct Meta {
+ /// An optional label attached to the element.
+ pub label: Option<Label>,
+ /// The element's location which identifies it in the laid-out output.
+ pub location: Option<Location>,
+ /// Manages the element during realization.
+ /// - If bit 0 is set, the element is prepared.
+ /// - If bit n is set, the element is guarded against the n-th show rule
+ /// recipe from the top of the style chain (counting from 1).
+ pub lifecycle: SmallBitSet,
+}
+
+impl RawContent {
+ /// Creates raw content wrapping an element, with all metadata set to
+ /// default (including a detached span).
+ pub(super) fn new<E: NativeElement>(data: E) -> Self {
+ Self::create(
+ data,
+ Meta {
+ label: None,
+ location: None,
+ lifecycle: SmallBitSet::new(),
+ },
+ HashLock::new(),
+ Span::detached(),
+ )
+ }
+
+ /// Creates and allocates raw content.
+ fn create<E: NativeElement>(data: E, meta: Meta, hash: HashLock, span: Span) -> Self {
+ let raw = Box::into_raw(Box::<Inner<E>>::new(Inner {
+ header: Header { refs: AtomicUsize::new(1), meta, hash },
+ data,
+ }));
+
+ // Safety: `Box` always holds a non-null pointer. See also
+ // `Box::into_non_null` (which is unstable).
+ let non_null = unsafe { NonNull::new_unchecked(raw) };
+
+ // Safety: See `RawContent::ptr`.
+ let ptr = non_null.cast::<Header>();
+
+ Self { ptr, elem: E::ELEM, span }
+ }
+
+ /// Destroys raw content and deallocates.
+ ///
+ /// # Safety
+ /// - The reference count must be zero.
+ /// - The raw content must be be of type `E`.
+ pub(super) unsafe fn drop_impl<E: NativeElement>(&mut self) {
+ debug_assert_eq!(self.header().refs.load(Ordering::Relaxed), 0);
+
+ // Safety:
+ // - The caller guarantees that the content is of type `E`.
+ // - Thus, `ptr` must have been created from `Box<Inner<E>>` (see
+ // `RawContent::ptr`).
+ // - And to clean it up, we can just reproduce our box.
+ unsafe {
+ let ptr = self.ptr.cast::<Inner<E>>();
+ drop(Box::<Inner<E>>::from_raw(ptr.as_ptr()));
+ }
+ }
+
+ /// Clones a packed element into new raw content.
+ pub(super) fn clone_impl<E: NativeElement>(elem: &Packed<E>) -> Self {
+ let raw = &elem.as_content().0;
+ let header = raw.header();
+ RawContent::create(
+ elem.as_ref().clone(),
+ header.meta.clone(),
+ header.hash.clone(),
+ raw.span,
+ )
+ }
+
+ /// Accesses the header part of the raw content.
+ fn header(&self) -> &Header {
+ // Safety: `self.ptr` is a valid pointer to a header structure.
+ unsafe { self.ptr.as_ref() }
+ }
+
+ /// Mutably accesses the header part of the raw content.
+ fn header_mut(&mut self) -> &mut Header {
+ self.make_unique();
+
+ // Safety:
+ // - `self.ptr` is a valid pointer to a header structure.
+ // - We have unique access to the backing allocation (just ensured).
+ unsafe { self.ptr.as_mut() }
+ }
+
+ /// Retrieves the contained element **without checking that the content is
+ /// of the correct type.**
+ ///
+ /// # Safety
+ /// This must be preceded by a check to [`is`]. The safe API for this is
+ /// [`Content::to_packed`] and the [`Packed`] struct.
+ pub(super) unsafe fn data<E: NativeElement>(&self) -> &E {
+ debug_assert!(self.is::<E>());
+
+ // Safety:
+ // - The caller guarantees that the content is of type `E`.
+ // - `self.ptr` is a valid pointer to an `Inner<E>` (see
+ // `RawContent::ptr`).
+ unsafe { &self.ptr.cast::<Inner<E>>().as_ref().data }
+ }
+
+ /// Retrieves the contained element mutably **without checking that the
+ /// content is of the correct type.**
+ ///
+ /// Ensures that the element's allocation is unique.
+ ///
+ /// # Safety
+ /// This must be preceded by a check to [`is`]. The safe API for this is
+ /// [`Content::to_packed_mut`] and the [`Packed`] struct.
+ pub(super) unsafe fn data_mut<E: NativeElement>(&mut self) -> &mut E {
+ debug_assert!(self.is::<E>());
+
+ // Ensure that the memoized hash is reset because we may mutate the
+ // element.
+ self.header_mut().hash.reset();
+
+ // Safety:
+ // - The caller guarantees that the content is of type `E`.
+ // - `self.ptr` is a valid pointer to an `Inner<E>` (see
+ // `RawContent::ptr`).
+ // - We have unique access to the backing allocation (due to header_mut).
+ unsafe { &mut self.ptr.cast::<Inner<E>>().as_mut().data }
+ }
+
+ /// Ensures that we have unique access to the backing allocation by cloning
+ /// if the reference count exceeds 1. This is used before performing
+ /// mutable operations, implementing a clone-on-write scheme.
+ fn make_unique(&mut self) {
+ if self.header().refs.load(Ordering::Relaxed) > 1 {
+ *self = self.handle().clone();
+ }
+ }
+
+ /// Retrieves the element this content is for.
+ pub(super) fn elem(&self) -> Element {
+ self.elem
+ }
+
+ /// Whether this content holds an element of type `E`.
+ pub(super) fn is<E: NativeElement>(&self) -> bool {
+ self.elem == E::ELEM
+ }
+
+ /// Retrieves the content's span.
+ pub(super) fn span(&self) -> Span {
+ self.span
+ }
+
+ /// Retrieves the content's span mutably.
+ pub(super) fn span_mut(&mut self) -> &mut Span {
+ &mut self.span
+ }
+
+ /// Retrieves the content's metadata.
+ pub(super) fn meta(&self) -> &Meta {
+ &self.header().meta
+ }
+
+ /// Retrieves the content's metadata mutably.
+ pub(super) fn meta_mut(&mut self) -> &mut Meta {
+ &mut self.header_mut().meta
+ }
+
+ /// Casts into a trait object for a given trait if the packed element
+ /// implements said trait.
+ pub(super) fn with<C>(&self) -> Option<&C>
+ where
+ C: ?Sized + 'static,
+ {
+ // Safety: The vtable comes from the `Capable` implementation which
+ // guarantees to return a matching vtable for `Packed<T>` and `C`. Since
+ // any `Packed<T>` is repr(transparent) with `Content` and `RawContent`,
+ // we can also use a `*const RawContent` pointer.
+ let vtable = (self.elem.vtable().capability)(TypeId::of::<C>())?;
+ let data = self as *const Self as *const ();
+ Some(unsafe { &*fat::from_raw_parts(data, vtable.as_ptr()) })
+ }
+
+ /// Casts into a mutable trait object for a given trait if the packed
+ /// element implements said trait.
+ pub(super) fn with_mut<C>(&mut self) -> Option<&mut C>
+ where
+ C: ?Sized + 'static,
+ {
+ // Safety: The vtable comes from the `Capable` implementation which
+ // guarantees to return a matching vtable for `Packed<T>` and `C`. Since
+ // any `Packed<T>` is repr(transparent) with `Content` and `RawContent`,
+ // we can also use a `*const Content` pointer.
+ //
+ // The resulting trait object contains an `&mut Packed<T>`. We do _not_
+ // need to ensure that we hold the only reference to the `Arc` here
+ // because `Packed<T>`'s DerefMut impl will take care of that if mutable
+ // access is required.
+ let vtable = (self.elem.vtable().capability)(TypeId::of::<C>())?;
+ let data = self as *mut Self as *mut ();
+ Some(unsafe { &mut *fat::from_raw_parts_mut(data, vtable.as_ptr()) })
+ }
+}
+
+impl RawContent {
+ /// Retrieves the element's vtable.
+ pub(super) fn handle(&self) -> vtable::ContentHandle<&RawContent> {
+ // Safety `self.elem.vtable()` is a matching vtable for `self`.
+ unsafe { vtable::Handle::new(self, self.elem.vtable()) }
+ }
+
+ /// Retrieves the element's vtable.
+ pub(super) fn handle_mut(&mut self) -> vtable::ContentHandle<&mut RawContent> {
+ // Safety `self.elem.vtable()` is a matching vtable for `self`.
+ unsafe { vtable::Handle::new(self, self.elem.vtable()) }
+ }
+
+ /// Retrieves the element's vtable.
+ pub(super) fn handle_pair<'a, 'b>(
+ &'a self,
+ other: &'b RawContent,
+ ) -> Option<vtable::ContentHandle<(&'a RawContent, &'b RawContent)>> {
+ (self.elem == other.elem).then(|| {
+ // Safety:
+ // - `self.elem.vtable()` is a matching vtable for `self`.
+ // - It's also matching for `other` because `self.elem == other.elem`.
+ unsafe { vtable::Handle::new((self, other), self.elem.vtable()) }
+ })
+ }
+}
+
+impl Debug for RawContent {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ self.handle().debug(f)
+ }
+}
+
+impl Clone for RawContent {
+ fn clone(&self) -> Self {
+ // See Arc's clone impl for details about memory ordering.
+ let prev = self.header().refs.fetch_add(1, Ordering::Relaxed);
+
+ // See Arc's clone impl details about guarding against incredibly
+ // degenerate programs.
+ if prev > isize::MAX as usize {
+ ref_count_overflow(self.ptr, self.elem, self.span);
+ }
+
+ Self { ptr: self.ptr, elem: self.elem, span: self.span }
+ }
+}
+
+impl Drop for RawContent {
+ fn drop(&mut self) {
+ // Drop our ref-count. If there was more than one content before
+ // (including this one), we shouldn't deallocate. See Arc's drop impl
+ // for details about memory ordering.
+ if self.header().refs.fetch_sub(1, Ordering::Release) != 1 {
+ return;
+ }
+
+ // See Arc's drop impl for details.
+ atomic::fence(Ordering::Acquire);
+
+ // Safety:
+ // No other content references the backing allocation (just checked)
+ unsafe {
+ self.handle_mut().drop();
+ }
+ }
+}
+
+impl PartialEq for RawContent {
+ fn eq(&self, other: &Self) -> bool {
+ let Some(handle) = self.handle_pair(other) else { return false };
+ handle
+ .eq()
+ .unwrap_or_else(|| handle.fields().all(|handle| handle.eq()))
+ }
+}
+
+impl Hash for RawContent {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.elem.hash(state);
+ let header = self.header();
+ header.meta.hash(state);
+ header.hash.get_or_insert_with(|| self.handle().hash()).hash(state);
+ self.span.hash(state);
+ }
+}
+
+// Safety:
+// - Works like `Arc`.
+// - `NativeElement` implies `Send` and `Sync`, see below.
+unsafe impl Sync for RawContent {}
+unsafe impl Send for RawContent {}
+
+fn _ensure_send_sync<T: NativeElement>() {
+ fn needs_send_sync<T: Send + Sync>() {}
+ needs_send_sync::<T>();
+}
+
+#[cold]
+fn ref_count_overflow(ptr: NonNull<Header>, elem: Element, span: Span) -> ! {
+ // Drop to decrement the ref count to counter the increment in `clone()`
+ drop(RawContent { ptr, elem, span });
+ panic!("reference count overflow");
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::foundations::{NativeElement, Repr, StyleChain, Value};
+ use crate::introspection::Location;
+ use crate::model::HeadingElem;
+ use crate::text::TextElem;
+
+ #[test]
+ fn test_miri() {
+ let styles = StyleChain::default();
+
+ let mut first = HeadingElem::new(TextElem::packed("Hi!")).with_offset(2).pack();
+ let hash1 = typst_utils::hash128(&first);
+ first.set_location(Location::new(10));
+ let _ = format!("{first:?}");
+ let _ = first.repr();
+
+ assert!(first.is::<HeadingElem>());
+ assert!(!first.is::<TextElem>());
+ assert_eq!(first.to_packed::<TextElem>(), None);
+ assert_eq!(first.location(), Some(Location::new(10)));
+ assert_eq!(first.field_by_name("offset"), Ok(Value::Int(2)));
+ assert!(!first.has("depth".into()));
+
+ let second = first.clone();
+ first.materialize(styles);
+
+ let first_packed = first.to_packed::<HeadingElem>().unwrap();
+ let second_packed = second.to_packed::<HeadingElem>().unwrap();
+
+ assert!(first.has("depth".into()));
+ assert!(!second.has("depth".into()));
+ assert!(first_packed.depth.is_set());
+ assert!(!second_packed.depth.is_set());
+ assert_ne!(first, second);
+ assert_ne!(hash1, typst_utils::hash128(&first));
+ }
+}
diff --git a/crates/typst-library/src/foundations/content/vtable.rs b/crates/typst-library/src/foundations/content/vtable.rs
new file mode 100644
index 00000000..fdc9d423
--- /dev/null
+++ b/crates/typst-library/src/foundations/content/vtable.rs
@@ -0,0 +1,383 @@
+//! A custom [vtable] implementation for content.
+//!
+//! This is similar to what is generated by the Rust compiler under the hood
+//! when using trait objects. However, ours has two key advantages:
+//!
+//! - It can store a _slice_ of sub-vtables for field-specific operations.
+//! - It can store not only methods, but also plain data, allowing us to access
+//! that data without going through dynamic dispatch.
+//!
+//! Because our vtable pointers are backed by `static` variables, we can also
+//! perform checks for element types by comparing raw vtable pointers giving us
+//! `RawContent::is` without dynamic dispatch.
+//!
+//! Overall, the custom vtable gives us just a little more flexibility and
+//! optimizability than using built-in trait objects.
+//!
+//! Note that all vtable methods receive elements of type `Packed<E>`, but some
+//! only perform actions on the `E` itself, with the shared part kept outside of
+//! the vtable (e.g. `hash`), while some perform the full action (e.g. `clone`
+//! as it needs to return new, fully populated raw content). Which one it is, is
+//! documented for each.
+//!
+//! # Safety
+//! This module contains a lot of `unsafe` keywords, but almost all of it is the
+//! same and quite straightfoward. All function pointers that operate on a
+//! specific element type are marked as unsafe. In combination with `repr(C)`,
+//! this grants us the ability to safely transmute a `ContentVtable<Packed<E>>`
+//! into a `ContentVtable<RawContent>` (or just short `ContentVtable`). Callers
+//! of functions marked as unsafe have to guarantee that the `ContentVtable` was
+//! transmuted from the same `E` as the RawContent was constructed from. The
+//! `Handle` struct provides a safe access layer, moving the guarantee that the
+//! vtable is matching into a single spot.
+//!
+//! [vtable]: https://en.wikipedia.org/wiki/Virtual_method_table
+
+use std::any::TypeId;
+use std::fmt::{self, Debug, Formatter};
+use std::ops::Deref;
+use std::ptr::NonNull;
+
+use ecow::EcoString;
+
+use super::raw::RawContent;
+use crate::diag::SourceResult;
+use crate::engine::Engine;
+use crate::foundations::{
+ Args, CastInfo, Construct, Content, LazyElementStore, NativeElement, NativeScope,
+ Packed, Repr, Scope, Set, StyleChain, Styles, Value,
+};
+use crate::text::{Lang, LocalName, Region};
+
+/// Encapsulates content and a vtable, granting safe access to vtable operations.
+pub(super) struct Handle<T, V: 'static>(T, &'static V);
+
+impl<T, V> Handle<T, V> {
+ /// Produces a new handle from content and a vtable.
+ ///
+ /// # Safety
+ /// The content and vtable must be matching, i.e. `vtable` must be derived
+ /// from the content's vtable.
+ pub(super) unsafe fn new(content: T, vtable: &'static V) -> Self {
+ Self(content, vtable)
+ }
+}
+
+impl<T, V> Deref for Handle<T, V> {
+ type Target = V;
+
+ fn deref(&self) -> &Self::Target {
+ self.1
+ }
+}
+
+pub(super) type ContentHandle<T> = Handle<T, ContentVtable>;
+pub(super) type FieldHandle<T> = Handle<T, FieldVtable>;
+
+/// A vtable for performing element-specific actions on type-erased content.
+/// Also contains general metadata for the specific element.
+#[repr(C)]
+pub struct ContentVtable<T: 'static = RawContent> {
+ /// The element's normal name, as in code.
+ pub(super) name: &'static str,
+ /// The element's title-cased name.
+ pub(super) title: &'static str,
+ /// The element's documentation (as Markdown).
+ pub(super) docs: &'static str,
+ /// Search keywords for the documentation.
+ pub(super) keywords: &'static [&'static str],
+
+ /// Subvtables for all fields of the element.
+ pub(super) fields: &'static [FieldVtable<T>],
+ /// Determines the ID for a field name. This is a separate function instead
+ /// of searching through `fields` so that Rust can generate optimized code
+ /// for the string matching.
+ pub(super) field_id: fn(name: &str) -> Option<u8>,
+
+ /// The constructor of the element.
+ pub(super) construct: fn(&mut Engine, &mut Args) -> SourceResult<Content>,
+ /// The set rule of the element.
+ pub(super) set: fn(&mut Engine, &mut Args) -> SourceResult<Styles>,
+ /// The element's local name in a specific lang-region pairing.
+ pub(super) local_name: Option<fn(Lang, Option<Region>) -> &'static str>,
+ /// Produces the associated [`Scope`] of the element.
+ pub(super) scope: fn() -> Scope,
+ /// If the `capability` function returns `Some(p)`, then `p` must be a valid
+ /// pointer to a native Rust vtable of `Packed<Self>` w.r.t to the trait `C`
+ /// where `capability` is `TypeId::of::<dyn C>()`.
+ pub(super) capability: fn(capability: TypeId) -> Option<NonNull<()>>,
+
+ /// The `Drop` impl (for the whole raw content). The content must have a
+ /// reference count of zero and may not be used anymore after `drop` was
+ /// called.
+ pub(super) drop: unsafe fn(&mut RawContent),
+ /// The `Clone` impl (for the whole raw content).
+ pub(super) clone: unsafe fn(&T) -> RawContent,
+ /// The `Hash` impl (for just the element).
+ pub(super) hash: unsafe fn(&T) -> u128,
+ /// The `Debug` impl (for just the element).
+ pub(super) debug: unsafe fn(&T, &mut Formatter) -> fmt::Result,
+ /// The `PartialEq` impl (for just the element). If this is `None`,
+ /// field-wise equality checks (via `FieldVtable`) should be performed.
+ pub(super) eq: Option<unsafe fn(&T, &T) -> bool>,
+ /// The `Repr` impl (for just the element). If this is `None`, a generic
+ /// name + fields representation should be produced.
+ pub(super) repr: Option<unsafe fn(&T) -> EcoString>,
+
+ /// Produces a reference to a `static` variable holding a `LazyElementStore`
+ /// that is unique for this element and can be populated with data that is
+ /// somewhat costly to initialize at runtime and shouldn't be initialized
+ /// over and over again. Must be a function rather than a direct reference
+ /// so that we can store the vtable in a `const` without Rust complaining
+ /// about the presence of interior mutability.
+ pub(super) store: fn() -> &'static LazyElementStore,
+}
+
+impl ContentVtable {
+ /// Creates the vtable for an element.
+ pub const fn new<E: NativeElement>(
+ name: &'static str,
+ title: &'static str,
+ docs: &'static str,
+ fields: &'static [FieldVtable<Packed<E>>],
+ field_id: fn(name: &str) -> Option<u8>,
+ capability: fn(TypeId) -> Option<NonNull<()>>,
+ store: fn() -> &'static LazyElementStore,
+ ) -> ContentVtable<Packed<E>> {
+ ContentVtable {
+ name,
+ title,
+ docs,
+ keywords: &[],
+ fields,
+ field_id,
+ construct: <E as Construct>::construct,
+ set: <E as Set>::set,
+ local_name: None,
+ scope: || Scope::new(),
+ capability,
+ drop: RawContent::drop_impl::<E>,
+ clone: RawContent::clone_impl::<E>,
+ hash: |elem| typst_utils::hash128(elem.as_ref()),
+ debug: |elem, f| Debug::fmt(elem.as_ref(), f),
+ eq: None,
+ repr: None,
+ store,
+ }
+ }
+
+ /// Retrieves the vtable of the element with the given ID.
+ pub fn field(&self, id: u8) -> Option<&'static FieldVtable> {
+ self.fields.get(usize::from(id))
+ }
+}
+
+impl<E: NativeElement> ContentVtable<Packed<E>> {
+ /// Attaches search keywords for the documentation.
+ pub const fn with_keywords(mut self, keywords: &'static [&'static str]) -> Self {
+ self.keywords = keywords;
+ self
+ }
+
+ /// Takes a [`Repr`] impl into account.
+ pub const fn with_repr(mut self) -> Self
+ where
+ E: Repr,
+ {
+ self.repr = Some(|e| E::repr(&**e));
+ self
+ }
+
+ /// Takes a [`PartialEq`] impl into account.
+ pub const fn with_partial_eq(mut self) -> Self
+ where
+ E: PartialEq,
+ {
+ self.eq = Some(|a, b| E::eq(&**a, &**b));
+ self
+ }
+
+ /// Takes a [`LocalName`] impl into account.
+ pub const fn with_local_name(mut self) -> Self
+ where
+ Packed<E>: LocalName,
+ {
+ self.local_name = Some(<Packed<E> as LocalName>::local_name);
+ self
+ }
+
+ /// Takes a [`NativeScope`] impl into account.
+ pub const fn with_scope(mut self) -> Self
+ where
+ E: NativeScope,
+ {
+ self.scope = || E::scope();
+ self
+ }
+
+ /// Type-erases the data.
+ pub const fn erase(self) -> ContentVtable {
+ // Safety:
+ // - `ContentVtable` is `repr(C)`.
+ // - `ContentVtable` does not hold any `E`-specific data except for
+ // function pointers.
+ // - All functions pointers have the same memory layout.
+ // - All functions containing `E` are marked as unsafe and callers need
+ // to uphold the guarantee that they only call them with raw content
+ // that is of type `E`.
+ // - `Packed<E>` and `RawContent` have the exact same memory layout
+ // because of `repr(transparent)`.
+ unsafe {
+ std::mem::transmute::<ContentVtable<Packed<E>>, ContentVtable<RawContent>>(
+ self,
+ )
+ }
+ }
+}
+
+impl<T> ContentHandle<T> {
+ /// Provides safe access to operations for the field with the given `id`.
+ pub(super) fn field(self, id: u8) -> Option<FieldHandle<T>> {
+ self.fields.get(usize::from(id)).map(|vtable| {
+ // Safety: Field vtables are of same type as the content vtable.
+ unsafe { Handle::new(self.0, vtable) }
+ })
+ }
+
+ /// Provides safe access to all field operations.
+ pub(super) fn fields(self) -> impl Iterator<Item = FieldHandle<T>>
+ where
+ T: Copy,
+ {
+ self.fields.iter().map(move |vtable| {
+ // Safety: Field vtables are of same type as the content vtable.
+ unsafe { Handle::new(self.0, vtable) }
+ })
+ }
+}
+
+impl ContentHandle<&RawContent> {
+ /// See [`ContentVtable::debug`].
+ pub fn debug(&self, f: &mut Formatter) -> fmt::Result {
+ // Safety: `Handle` has the invariant that the vtable is matching.
+ unsafe { (self.1.debug)(self.0, f) }
+ }
+
+ /// See [`ContentVtable::repr`].
+ pub fn repr(&self) -> Option<EcoString> {
+ // Safety: `Handle` has the invariant that the vtable is matching.
+ unsafe { self.1.repr.map(|f| f(self.0)) }
+ }
+
+ /// See [`ContentVtable::clone`].
+ pub fn clone(&self) -> RawContent {
+ // Safety: `Handle` has the invariant that the vtable is matching.
+ unsafe { (self.1.clone)(self.0) }
+ }
+
+ /// See [`ContentVtable::hash`].
+ pub fn hash(&self) -> u128 {
+ // Safety: `Handle` has the invariant that the vtable is matching.
+ unsafe { (self.1.hash)(self.0) }
+ }
+}
+
+impl ContentHandle<&mut RawContent> {
+ /// See [`ContentVtable::drop`].
+ pub unsafe fn drop(&mut self) {
+ // Safety:
+ // - `Handle` has the invariant that the vtable is matching.
+ // - The caller satifies the requirements of `drop`
+ unsafe { (self.1.drop)(self.0) }
+ }
+}
+
+impl ContentHandle<(&RawContent, &RawContent)> {
+ /// See [`ContentVtable::eq`].
+ pub fn eq(&self) -> Option<bool> {
+ // Safety: `Handle` has the invariant that the vtable is matching.
+ let (a, b) = self.0;
+ unsafe { self.1.eq.map(|f| f(a, b)) }
+ }
+}
+
+/// A vtable for performing field-specific actions on type-erased
+/// content. Also contains general metadata for the specific field.
+#[repr(C)]
+pub struct FieldVtable<T: 'static = RawContent> {
+ /// The field's name, as in code.
+ pub(super) name: &'static str,
+ /// The fields's documentation (as Markdown).
+ pub(super) docs: &'static str,
+
+ /// Whether the field's parameter is positional.
+ pub(super) positional: bool,
+ /// Whether the field's parameter is variadic.
+ pub(super) variadic: bool,
+ /// Whether the field's parameter is required.
+ pub(super) required: bool,
+ /// Whether the field can be set via a set rule.
+ pub(super) settable: bool,
+ /// Whether the field is synthesized (i.e. initially not present).
+ pub(super) synthesized: bool,
+ /// Reflects what types the field's parameter accepts.
+ pub(super) input: fn() -> CastInfo,
+ /// Produces the default value of the field, if any. This would e.g. be
+ /// `None` for a required parameter.
+ pub(super) default: Option<fn() -> Value>,
+
+ /// Whether the field is set on the given element. Always true for required
+ /// fields, but can be false for settable or synthesized fields.
+ pub(super) has: unsafe fn(elem: &T) -> bool,
+ /// Retrieves the field and [turns it into a
+ /// value](crate::foundations::IntoValue).
+ pub(super) get: unsafe fn(elem: &T) -> Option<Value>,
+ /// Retrieves the field given styles. The resulting value may come from the
+ /// element, the style chain, or a mix (if it's a
+ /// [`Fold`](crate::foundations::Fold) field).
+ pub(super) get_with_styles: unsafe fn(elem: &T, StyleChain) -> Option<Value>,
+ /// Retrieves the field just from the styles.
+ pub(super) get_from_styles: fn(StyleChain) -> Option<Value>,
+ /// Sets the field from the styles if it is currently unset. (Or merges
+ /// with the style data in case of a `Fold` field).
+ pub(super) materialize: unsafe fn(elem: &mut T, styles: StyleChain),
+ /// Compares the field for equality.
+ pub(super) eq: unsafe fn(a: &T, b: &T) -> bool,
+}
+
+impl FieldHandle<&RawContent> {
+ /// See [`FieldVtable::has`].
+ pub fn has(&self) -> bool {
+ // Safety: `Handle` has the invariant that the vtable is matching.
+ unsafe { (self.1.has)(self.0) }
+ }
+
+ /// See [`FieldVtable::get`].
+ pub fn get(&self) -> Option<Value> {
+ // Safety: `Handle` has the invariant that the vtable is matching.
+ unsafe { (self.1.get)(self.0) }
+ }
+
+ /// See [`FieldVtable::get_with_styles`].
+ pub fn get_with_styles(&self, styles: StyleChain) -> Option<Value> {
+ // Safety: `Handle` has the invariant that the vtable is matching.
+ unsafe { (self.1.get_with_styles)(self.0, styles) }
+ }
+}
+
+impl FieldHandle<&mut RawContent> {
+ /// See [`FieldVtable::materialize`].
+ pub fn materialize(&mut self, styles: StyleChain) {
+ // Safety: `Handle` has the invariant that the vtable is matching.
+ unsafe { (self.1.materialize)(self.0, styles) }
+ }
+}
+
+impl FieldHandle<(&RawContent, &RawContent)> {
+ /// See [`FieldVtable::eq`].
+ pub fn eq(&self) -> bool {
+ // Safety: `Handle` has the invariant that the vtable is matching.
+ let (a, b) = self.0;
+ unsafe { (self.1.eq)(a, b) }
+ }
+}
diff --git a/crates/typst-library/src/foundations/mod.rs b/crates/typst-library/src/foundations/mod.rs
index 6840f855..382beb2c 100644
--- a/crates/typst-library/src/foundations/mod.rs
+++ b/crates/typst-library/src/foundations/mod.rs
@@ -17,7 +17,6 @@ mod datetime;
mod decimal;
mod dict;
mod duration;
-mod element;
mod fields;
mod float;
mod func;
@@ -49,7 +48,6 @@ pub use self::datetime::*;
pub use self::decimal::*;
pub use self::dict::*;
pub use self::duration::*;
-pub use self::element::*;
pub use self::fields::*;
pub use self::float::*;
pub use self::func::*;
diff --git a/crates/typst-library/src/foundations/scope.rs b/crates/typst-library/src/foundations/scope.rs
index e1ce61b8..838584cc 100644
--- a/crates/typst-library/src/foundations/scope.rs
+++ b/crates/typst-library/src/foundations/scope.rs
@@ -8,8 +8,7 @@ use typst_syntax::Span;
use crate::diag::{bail, DeprecationSink, HintedStrResult, HintedString, StrResult};
use crate::foundations::{
- Element, Func, IntoValue, NativeElement, NativeFunc, NativeFuncData, NativeType,
- Type, Value,
+ Func, IntoValue, NativeElement, NativeFunc, NativeFuncData, NativeType, Value,
};
use crate::{Category, Library};
@@ -149,15 +148,15 @@ impl Scope {
/// Define a native type.
#[track_caller]
pub fn define_type<T: NativeType>(&mut self) -> &mut Binding {
- let data = T::data();
- self.define(data.name, Type::from(data))
+ let ty = T::ty();
+ self.define(ty.short_name(), ty)
}
/// Define a native element.
#[track_caller]
pub fn define_elem<T: NativeElement>(&mut self) -> &mut Binding {
- let data = T::data();
- self.define(data.name, Element::from(data))
+ let elem = T::ELEM;
+ self.define(elem.name(), elem)
}
/// Define a built-in with compile-time known name and returns a mutable
diff --git a/crates/typst-library/src/foundations/selector.rs b/crates/typst-library/src/foundations/selector.rs
index bf5449d9..8f264a81 100644
--- a/crates/typst-library/src/foundations/selector.rs
+++ b/crates/typst-library/src/foundations/selector.rs
@@ -21,12 +21,12 @@ macro_rules! __select_where {
let mut fields = ::smallvec::SmallVec::new();
$(
fields.push((
- <$ty as $crate::foundations::Fields>::Enum::$field as u8,
+ <$ty>::$field.index(),
$crate::foundations::IntoValue::into_value($value),
));
)*
$crate::foundations::Selector::Elem(
- <$ty as $crate::foundations::NativeElement>::elem(),
+ <$ty as $crate::foundations::NativeElement>::ELEM,
Some(fields),
)
}};
diff --git a/crates/typst-library/src/foundations/styles.rs b/crates/typst-library/src/foundations/styles.rs
index d124f2c8..978b47d5 100644
--- a/crates/typst-library/src/foundations/styles.rs
+++ b/crates/typst-library/src/foundations/styles.rs
@@ -12,8 +12,8 @@ use typst_utils::LazyHash;
use crate::diag::{SourceResult, Trace, Tracepoint};
use crate::engine::Engine;
use crate::foundations::{
- cast, ty, Content, Context, Element, Func, NativeElement, OneOrMultiple, Repr,
- Selector,
+ cast, ty, Content, Context, Element, Field, Func, NativeElement, OneOrMultiple,
+ RefableProperty, Repr, Selector, SettableProperty,
};
use crate::text::{FontFamily, FontList, TextElem};
@@ -48,7 +48,16 @@ impl Styles {
/// If the property needs folding and the value is already contained in the
/// style map, `self` contributes the outer values and `value` is the inner
/// one.
- pub fn set(&mut self, style: impl Into<Style>) {
+ pub fn set<E, const I: u8>(&mut self, field: Field<E, I>, value: E::Type)
+ where
+ E: SettableProperty<I>,
+ E::Type: Debug + Clone + Hash + Send + Sync + 'static,
+ {
+ self.push(Property::new(field, value));
+ }
+
+ /// Add a new style to the list.
+ pub fn push(&mut self, style: impl Into<Style>) {
self.0.push(LazyHash::new(style.into()));
}
@@ -101,22 +110,25 @@ impl Styles {
}
/// Whether there is a style for the given field of the given element.
- pub fn has<T: NativeElement>(&self, field: u8) -> bool {
- let elem = T::elem();
+ pub fn has<E: NativeElement, const I: u8>(&self, _: Field<E, I>) -> bool {
+ let elem = E::ELEM;
self.0
.iter()
.filter_map(|style| style.property())
- .any(|property| property.is_of(elem) && property.id == field)
+ .any(|property| property.is_of(elem) && property.id == I)
}
/// Set a font family composed of a preferred family and existing families
/// from a style chain.
pub fn set_family(&mut self, preferred: FontFamily, existing: StyleChain) {
- self.set(TextElem::set_font(FontList(
- std::iter::once(preferred)
- .chain(TextElem::font_in(existing).into_iter().cloned())
- .collect(),
- )));
+ self.set(
+ TextElem::font,
+ FontList(
+ std::iter::once(preferred)
+ .chain(existing.get_ref(TextElem::font).into_iter().cloned())
+ .collect(),
+ ),
+ );
}
}
@@ -281,14 +293,14 @@ pub struct Property {
impl Property {
/// Create a new property from a key-value pair.
- pub fn new<E, T>(id: u8, value: T) -> Self
+ pub fn new<E, const I: u8>(_: Field<E, I>, value: E::Type) -> Self
where
- E: NativeElement,
- T: Debug + Clone + Hash + Send + Sync + 'static,
+ E: SettableProperty<I>,
+ E::Type: Debug + Clone + Hash + Send + Sync + 'static,
{
Self {
- elem: E::elem(),
- id,
+ elem: E::ELEM,
+ id: I,
value: Block::new(value),
span: Span::detached(),
liftable: false,
@@ -340,8 +352,11 @@ impl Block {
}
/// Downcasts the block to the specified type.
- fn downcast<T: 'static>(&self) -> Option<&T> {
- self.0.as_any().downcast_ref()
+ fn downcast<T: 'static>(&self, func: Element, id: u8) -> &T {
+ self.0
+ .as_any()
+ .downcast_ref()
+ .unwrap_or_else(|| block_wrong_type(func, id, self))
}
}
@@ -528,90 +543,113 @@ impl<'a> StyleChain<'a> {
Self { head: &root.0, tail: None }
}
- /// Make the given chainable the first link of this chain.
+ /// Retrieves the value of the given field from the style chain.
///
- /// The resulting style chain contains styles from `local` as well as
- /// `self`. The ones from `local` take precedence over the ones from
- /// `self`. For folded properties `local` contributes the inner value.
- pub fn chain<'b, C>(&'b self, local: &'b C) -> StyleChain<'b>
+ /// A `Field` value is a zero-sized value that specifies which field of an
+ /// element you want to retrieve on the type-system level. It also ensures
+ /// that Rust can infer the correct return type.
+ ///
+ /// Should be preferred over [`get_cloned`](Self::get_cloned) or
+ /// [`get_ref`](Self::get_ref), but is only available for [`Copy`] types.
+ /// For other types an explicit decision needs to be made whether cloning is
+ /// necessary.
+ pub fn get<E, const I: u8>(self, field: Field<E, I>) -> E::Type
where
- C: Chainable + ?Sized,
+ E: SettableProperty<I>,
+ E::Type: Copy,
{
- Chainable::chain(local, self)
+ self.get_cloned(field)
}
- /// Cast the first value for the given property in the chain.
- pub fn get<T: Clone + 'static>(
- self,
- func: Element,
- id: u8,
- inherent: Option<&T>,
- default: impl Fn() -> T,
- ) -> T {
- self.properties::<T>(func, id, inherent)
- .next()
- .cloned()
- .unwrap_or_else(default)
+ /// Retrieves and clones the value from the style chain.
+ ///
+ /// Prefer [`get`](Self::get) if the type is `Copy` and
+ /// [`get_ref`](Self::get_ref) if a reference suffices.
+ pub fn get_cloned<E, const I: u8>(self, _: Field<E, I>) -> E::Type
+ where
+ E: SettableProperty<I>,
+ {
+ if let Some(fold) = E::FOLD {
+ self.get_folded::<E::Type>(E::ELEM, I, fold, E::default())
+ } else {
+ self.get_unfolded::<E::Type>(E::ELEM, I)
+ .cloned()
+ .unwrap_or_else(E::default)
+ }
}
- /// Cast the first value for the given property in the chain,
- /// returning a borrowed value.
- pub fn get_ref<T: 'static>(
+ /// Retrieves a reference to the value of the given field from the style
+ /// chain.
+ ///
+ /// Not possible if the value needs folding.
+ pub fn get_ref<E, const I: u8>(self, _: Field<E, I>) -> &'a E::Type
+ where
+ E: RefableProperty<I>,
+ {
+ self.get_unfolded(E::ELEM, I).unwrap_or_else(|| E::default_ref())
+ }
+
+ /// Retrieves the value and then immediately [resolves](Resolve) it.
+ pub fn resolve<E, const I: u8>(
self,
- func: Element,
- id: u8,
- inherent: Option<&'a T>,
- default: impl Fn() -> &'a T,
- ) -> &'a T {
- self.properties::<T>(func, id, inherent)
- .next()
- .unwrap_or_else(default)
+ field: Field<E, I>,
+ ) -> <E::Type as Resolve>::Output
+ where
+ E: SettableProperty<I>,
+ E::Type: Resolve,
+ {
+ self.get_cloned(field).resolve(self)
+ }
+
+ /// Retrieves a reference to a field, also taking into account the
+ /// instance's value if any.
+ fn get_unfolded<T: 'static>(self, func: Element, id: u8) -> Option<&'a T> {
+ self.find(func, id).map(|block| block.downcast(func, id))
}
- /// Cast the first value for the given property in the chain, taking
- /// `Fold` implementations into account.
- pub fn get_folded<T: Fold + Clone + 'static>(
+ /// Retrieves a reference to a field, also taking into account the
+ /// instance's value if any.
+ fn get_folded<T: 'static + Clone>(
self,
func: Element,
id: u8,
- inherent: Option<&T>,
- default: impl Fn() -> T,
+ fold: fn(T, T) -> T,
+ default: T,
) -> T {
- fn next<T: Fold>(
- mut values: impl Iterator<Item = T>,
- default: &impl Fn() -> T,
- ) -> T {
- values
- .next()
- .map(|value| value.fold(next(values, default)))
- .unwrap_or_else(default)
+ let iter = self
+ .properties(func, id)
+ .map(|block| block.downcast::<T>(func, id).clone());
+
+ if let Some(folded) = iter.reduce(fold) {
+ fold(folded, default)
+ } else {
+ default
}
- next(self.properties::<T>(func, id, inherent).cloned(), &default)
}
/// Iterate over all values for the given property in the chain.
- fn properties<T: 'static>(
- self,
- func: Element,
- id: u8,
- inherent: Option<&'a T>,
- ) -> impl Iterator<Item = &'a T> {
- inherent.into_iter().chain(
- self.entries()
- .filter_map(|style| style.property())
- .filter(move |property| property.is(func, id))
- .map(|property| &property.value)
- .map(move |value| {
- value.downcast().unwrap_or_else(|| {
- panic!(
- "attempted to read a value of a different type than was written {}.{}: {:?}",
- func.name(),
- func.field_name(id).unwrap(),
- value
- )
- })
- }),
- )
+ fn find(self, func: Element, id: u8) -> Option<&'a Block> {
+ self.properties(func, id).next()
+ }
+
+ /// Iterate over all values for the given property in the chain.
+ fn properties(self, func: Element, id: u8) -> impl Iterator<Item = &'a Block> {
+ self.entries()
+ .filter_map(|style| style.property())
+ .filter(move |property| property.is(func, id))
+ .map(|property| &property.value)
+ }
+
+ /// Make the given chainable the first link of this chain.
+ ///
+ /// The resulting style chain contains styles from `local` as well as
+ /// `self`. The ones from `local` take precedence over the ones from
+ /// `self`. For folded properties `local` contributes the inner value.
+ pub fn chain<'b, C>(&'b self, local: &'b C) -> StyleChain<'b>
+ where
+ C: Chainable + ?Sized,
+ {
+ Chainable::chain(local, self)
}
/// Iterate over the entries of the chain.
@@ -804,6 +842,9 @@ impl<T: Resolve> Resolve for Option<T> {
/// #set rect(stroke: 4pt)
/// #rect()
/// ```
+///
+/// Note: Folding must be associative, i.e. any implementation must satisfy
+/// `fold(fold(a, b), c) == fold(a, fold(b, c))`.
pub trait Fold {
/// Fold this inner value with an outer folded value.
fn fold(self, outer: Self) -> Self;
@@ -847,6 +888,9 @@ impl<T> Fold for OneOrMultiple<T> {
}
}
+/// A [folding](Fold) function.
+pub type FoldFn<T> = fn(T, T) -> T;
+
/// A variant of fold for foldable optional (`Option<T>`) values where an inner
/// `None` value isn't respected (contrary to `Option`'s usual `Fold`
/// implementation, with which folding with an inner `None` always returns
@@ -884,3 +928,13 @@ impl Fold for Depth {
Self(outer.0 + self.0)
}
}
+
+#[cold]
+fn block_wrong_type(func: Element, id: u8, value: &Block) -> ! {
+ panic!(
+ "attempted to read a value of a different type than was written {}.{}: {:?}",
+ func.name(),
+ func.field_name(id).unwrap(),
+ value
+ )
+}
diff --git a/crates/typst-library/src/foundations/target.rs b/crates/typst-library/src/foundations/target.rs
index 2a21fd42..71e7554e 100644
--- a/crates/typst-library/src/foundations/target.rs
+++ b/crates/typst-library/src/foundations/target.rs
@@ -73,5 +73,5 @@ pub struct TargetElem {
/// ```
#[func(contextual)]
pub fn target(context: Tracked<Context>) -> HintedStrResult<Target> {
- Ok(TargetElem::target_in(context.styles()?))
+ Ok(context.styles()?.get(TargetElem::target))
}