mirror of
https://github.com/typst/typst
synced 2025-08-19 01:18:32 +08:00
Compare commits
2 Commits
1fd9a10f35
...
19559466e5
Author | SHA1 | Date | |
---|---|---|---|
|
19559466e5 | ||
|
d12cad6e46 |
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@ -103,3 +103,15 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- run: cargo install --locked cargo-fuzz@0.12.0
|
||||
- run: cd tests/fuzz && cargo fuzz build --dev
|
||||
|
||||
miri:
|
||||
name: Check unsafe code
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
components: miri
|
||||
toolchain: nightly-2024-10-29
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- run: cargo miri test -p typst-library test_miri
|
||||
|
269
crates/typst-library/src/foundations/content/element.rs
Normal file
269
crates/typst-library/src/foundations/content/element.rs
Normal file
@ -0,0 +1,269 @@
|
||||
use std::any::TypeId;
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::{self, Debug};
|
||||
use std::hash::Hash;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use ecow::EcoString;
|
||||
use smallvec::SmallVec;
|
||||
use typst_utils::Static;
|
||||
|
||||
use crate::diag::SourceResult;
|
||||
use crate::engine::Engine;
|
||||
use crate::foundations::{
|
||||
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<ContentVtable>);
|
||||
|
||||
impl Element {
|
||||
/// Get the element for `T`.
|
||||
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.vtable().name
|
||||
}
|
||||
|
||||
/// The element's title case name, for use in documentation
|
||||
/// (e.g. `Numbered List`).
|
||||
pub fn title(&self) -> &'static str {
|
||||
self.vtable().title
|
||||
}
|
||||
|
||||
/// Documentation for the element (as Markdown).
|
||||
pub fn docs(&self) -> &'static str {
|
||||
self.vtable().docs
|
||||
}
|
||||
|
||||
/// Search keywords for the element.
|
||||
pub fn keywords(&self) -> &'static [&'static str] {
|
||||
self.vtable().keywords
|
||||
}
|
||||
|
||||
/// Construct an instance of this element.
|
||||
pub fn construct(
|
||||
self,
|
||||
engine: &mut Engine,
|
||||
args: &mut Args,
|
||||
) -> SourceResult<Content> {
|
||||
(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.vtable().set)(engine, &mut args)?;
|
||||
args.finish()?;
|
||||
Ok(styles)
|
||||
}
|
||||
|
||||
/// Whether the element has the given capability.
|
||||
pub fn can<C>(self) -> bool
|
||||
where
|
||||
C: ?Sized + 'static,
|
||||
{
|
||||
self.can_type_id(TypeId::of::<C>())
|
||||
}
|
||||
|
||||
/// 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.vtable().capability)(type_id).is_some()
|
||||
}
|
||||
|
||||
/// Create a selector for this element.
|
||||
pub fn select(self) -> Selector {
|
||||
Selector::Elem(self, None)
|
||||
}
|
||||
|
||||
/// Create a selector for this element, filtering for those that
|
||||
/// [fields](crate::foundations::Content::field) match the given argument.
|
||||
pub fn where_(self, fields: SmallVec<[(u8, Value); 1]>) -> Selector {
|
||||
Selector::Elem(self, Some(fields))
|
||||
}
|
||||
|
||||
/// The element's associated scope of sub-definition.
|
||||
pub fn scope(&self) -> &'static Scope {
|
||||
(self.vtable().store)().scope.get_or_init(|| (self.vtable().scope)())
|
||||
}
|
||||
|
||||
/// Details about the element's fields.
|
||||
pub fn params(&self) -> &'static [ParamInfo] {
|
||||
(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.
|
||||
pub fn field_id(&self, name: &str) -> Option<u8> {
|
||||
if name == "label" {
|
||||
return Some(255);
|
||||
}
|
||||
(self.vtable().field_id)(name)
|
||||
}
|
||||
|
||||
/// Extract the field name for the given field ID.
|
||||
pub fn field_name(&self, id: u8) -> Option<&'static str> {
|
||||
if id == 255 {
|
||||
return Some("label");
|
||||
}
|
||||
self.vtable().field(id).map(|data| data.name)
|
||||
}
|
||||
|
||||
/// Extract the value of the field for the given field ID and style chain.
|
||||
pub fn field_from_styles(
|
||||
&self,
|
||||
id: u8,
|
||||
styles: StyleChain,
|
||||
) -> Result<Value, FieldAccessError> {
|
||||
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.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
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Element {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Element({})", self.name())
|
||||
}
|
||||
}
|
||||
|
||||
impl Repr for Element {
|
||||
fn repr(&self) -> EcoString {
|
||||
self.name().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Element {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.name().cmp(other.name())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Element {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
cast! {
|
||||
Element,
|
||||
self => Value::Func(self.into()),
|
||||
v: Func => v.element().ok_or("expected element")?,
|
||||
}
|
||||
|
||||
/// Lazily initialized data for an element.
|
||||
#[derive(Default)]
|
||||
pub struct LazyElementStore {
|
||||
pub scope: OnceLock<Scope>,
|
||||
pub params: OnceLock<Vec<ParamInfo>>,
|
||||
}
|
||||
|
||||
impl LazyElementStore {
|
||||
/// Create an empty store.
|
||||
pub const fn new() -> Self {
|
||||
Self { scope: OnceLock::new(), params: OnceLock::new() }
|
||||
}
|
||||
}
|
||||
|
||||
/// A Typst element that is defined by a native Rust type.
|
||||
///
|
||||
/// # Safety
|
||||
/// `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;
|
||||
|
||||
/// Pack the element into type-erased content.
|
||||
fn pack(self) -> Content {
|
||||
Content::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// An element's constructor function.
|
||||
pub trait Construct {
|
||||
/// Construct an element from the arguments.
|
||||
///
|
||||
/// This is passed only the arguments that remain after execution of the
|
||||
/// element's set rule.
|
||||
fn construct(engine: &mut Engine, args: &mut Args) -> SourceResult<Content>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// An element's set rule.
|
||||
pub trait Set {
|
||||
/// Parse relevant arguments into style properties for this element.
|
||||
fn set(engine: &mut Engine, args: &mut Args) -> SourceResult<Styles>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// Synthesize fields on an element. This happens before execution of any show
|
||||
/// rule.
|
||||
pub trait Synthesize {
|
||||
/// Prepare the element for show rule application.
|
||||
fn synthesize(&mut self, engine: &mut Engine, styles: StyleChain)
|
||||
-> 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
|
||||
/// access the element's fields.
|
||||
pub trait ShowSet {
|
||||
/// Finalize the fully realized form of the element. Use this for effects
|
||||
/// 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);
|
||||
}
|
564
crates/typst-library/src/foundations/content/field.rs
Normal file
564
crates/typst-library/src/foundations/content/field.rs
Normal file
@ -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
|
||||
}
|
||||
}
|
@ -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, repr, scope, ty, Context, Dict, Element, Field, IntoValue, Label,
|
||||
NativeElement, Property, Recipe, RecipeIndex, Repr, Selector, SettableProperty, 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.
|
||||
@ -485,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(())
|
||||
@ -571,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
|
||||
@ -601,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());
|
||||
}
|
||||
@ -614,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
|
||||
}
|
||||
}
|
||||
|
||||
@ -626,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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -636,20 +608,15 @@ 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().unwrap_or_else(|| {
|
||||
self.0.handle().repr().unwrap_or_else(|| {
|
||||
let fields = self
|
||||
.0
|
||||
.handle()
|
||||
.fields()
|
||||
.into_iter()
|
||||
.map(|(name, value)| eco_format!("{}: {}", name, value.repr()))
|
||||
.filter_map(|field| field.get().map(|v| (field.name, v.repr())))
|
||||
.map(|(name, value)| eco_format!("{name}: {value}"))
|
||||
.collect::<Vec<_>>();
|
||||
eco_format!(
|
||||
"{}{}",
|
||||
@ -737,267 +704,6 @@ impl Serialize for Content {
|
||||
}
|
||||
}
|
||||
|
||||
/// The trait that combines all the other traits into a trait object.
|
||||
trait Bounds: Debug + 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;
|
||||
|
||||
fn has(&self, id: u8) -> bool;
|
||||
fn field(&self, id: u8) -> Result<Value, FieldAccessError>;
|
||||
fn field_with_styles(
|
||||
&self,
|
||||
id: u8,
|
||||
styles: StyleChain,
|
||||
) -> Result<Value, FieldAccessError>;
|
||||
fn materialize(&mut self, styles: StyleChain);
|
||||
fn fields(&self) -> Dict;
|
||||
fn repr(&self) -> Option<EcoString>;
|
||||
}
|
||||
|
||||
impl<T> Bounds for T
|
||||
where
|
||||
T: NativeElement,
|
||||
{
|
||||
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;
|
||||
};
|
||||
if let Some(f) = T::ELEMENT.eq {
|
||||
f(self, other)
|
||||
} else {
|
||||
T::ELEMENT.fields.iter().all(|field| (field.eq)(self, other))
|
||||
}
|
||||
}
|
||||
|
||||
fn has(&self, id: u8) -> bool {
|
||||
match T::ELEMENT.get(id) {
|
||||
Some(data) => (data.has)(self),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn field(&self, id: u8) -> Result<Value, FieldAccessError> {
|
||||
match T::ELEMENT.get(id) {
|
||||
Some(data) => (data.get)(self).ok_or(FieldAccessError::Unset),
|
||||
None => Err(FieldAccessError::Unknown),
|
||||
}
|
||||
}
|
||||
|
||||
fn field_with_styles(
|
||||
&self,
|
||||
id: u8,
|
||||
styles: StyleChain,
|
||||
) -> Result<Value, FieldAccessError> {
|
||||
match T::ELEMENT.get(id) {
|
||||
Some(data) => {
|
||||
(data.get_with_styles)(self, styles).ok_or(FieldAccessError::Unset)
|
||||
}
|
||||
None => Err(FieldAccessError::Unknown),
|
||||
}
|
||||
}
|
||||
|
||||
fn materialize(&mut self, styles: StyleChain) {
|
||||
for field in T::ELEMENT.fields {
|
||||
(field.materialize)(self, styles);
|
||||
}
|
||||
}
|
||||
|
||||
fn fields(&self) -> Dict {
|
||||
let mut dict = Dict::new();
|
||||
for field in T::ELEMENT.fields {
|
||||
if let Some(value) = (field.get)(self) {
|
||||
dict.insert(field.name.into(), value);
|
||||
}
|
||||
}
|
||||
dict
|
||||
}
|
||||
|
||||
fn repr(&self) -> Option<EcoString> {
|
||||
// Returns None if the element doesn't have a special `Repr` impl.
|
||||
// Then, we use a generic one based on `Self::fields`.
|
||||
T::ELEMENT.repr.map(|f| f(self))
|
||||
}
|
||||
}
|
||||
|
||||
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)]
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// A sequence of content.
|
||||
#[elem(Debug, Repr)]
|
||||
pub struct SequenceElem {
|
||||
@ -1067,42 +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,
|
||||
}
|
||||
|
||||
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
|
||||
impl<T: NativeElement> IntoValue for T {
|
||||
fn into_value(self) -> Value {
|
||||
Value::Content(self.pack())
|
||||
}
|
||||
}
|
147
crates/typst-library/src/foundations/content/packed.rs
Normal file
147
crates/typst-library/src/foundations/content/packed.rs
Normal file
@ -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);
|
||||
}
|
||||
}
|
426
crates/typst-library/src/foundations/content/raw.rs
Normal file
426
crates/typst-library/src/foundations/content/raw.rs
Normal file
@ -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));
|
||||
}
|
||||
}
|
383
crates/typst-library/src/foundations/content/vtable.rs
Normal file
383
crates/typst-library/src/foundations/content/vtable.rs
Normal file
@ -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) }
|
||||
}
|
||||
}
|
@ -1,952 +0,0 @@
|
||||
use std::any::TypeId;
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::{self, Debug};
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
use std::ptr::NonNull;
|
||||
use std::sync::{LazyLock, 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, CastInfo, Container, Content, FieldAccessError, Fold, Func, IntoValue,
|
||||
NativeScope, Packed, ParamInfo, Property, Reflect, Repr, Resolve, Scope, Selector,
|
||||
StyleChain, Styles, Value,
|
||||
};
|
||||
use crate::text::{Lang, LocalName, Region};
|
||||
|
||||
/// A document element.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Element(Static<NativeElementData>);
|
||||
|
||||
impl Element {
|
||||
/// Get the element for `T`.
|
||||
pub fn of<T: NativeElement>() -> Self {
|
||||
T::elem()
|
||||
}
|
||||
|
||||
/// The element's normal name (e.g. `enum`).
|
||||
pub fn name(self) -> &'static str {
|
||||
self.0.name
|
||||
}
|
||||
|
||||
/// The element's title case name, for use in documentation
|
||||
/// (e.g. `Numbered List`).
|
||||
pub fn title(&self) -> &'static str {
|
||||
self.0.title
|
||||
}
|
||||
|
||||
/// Documentation for the element (as Markdown).
|
||||
pub fn docs(&self) -> &'static str {
|
||||
self.0.docs
|
||||
}
|
||||
|
||||
/// Search keywords for the element.
|
||||
pub fn keywords(&self) -> &'static [&'static str] {
|
||||
self.0.keywords
|
||||
}
|
||||
|
||||
/// Construct an instance of this element.
|
||||
pub fn construct(
|
||||
self,
|
||||
engine: &mut Engine,
|
||||
args: &mut Args,
|
||||
) -> SourceResult<Content> {
|
||||
(self.0.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)?;
|
||||
args.finish()?;
|
||||
Ok(styles)
|
||||
}
|
||||
|
||||
/// Whether the element has the given capability.
|
||||
pub fn can<C>(self) -> bool
|
||||
where
|
||||
C: ?Sized + 'static,
|
||||
{
|
||||
self.can_type_id(TypeId::of::<C>())
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// Create a selector for this element.
|
||||
pub fn select(self) -> Selector {
|
||||
Selector::Elem(self, None)
|
||||
}
|
||||
|
||||
/// Create a selector for this element, filtering for those that
|
||||
/// [fields](crate::foundations::Content::field) match the given argument.
|
||||
pub fn where_(self, fields: SmallVec<[(u8, Value); 1]>) -> Selector {
|
||||
Selector::Elem(self, Some(fields))
|
||||
}
|
||||
|
||||
/// The element's associated scope of sub-definition.
|
||||
pub fn scope(&self) -> &'static Scope {
|
||||
&(self.0).0.scope
|
||||
}
|
||||
|
||||
/// Details about the element's fields.
|
||||
pub fn params(&self) -> &'static [ParamInfo] {
|
||||
&(self.0).0.params
|
||||
}
|
||||
|
||||
/// Extract the field ID for the given field name.
|
||||
pub fn field_id(&self, name: &str) -> Option<u8> {
|
||||
if name == "label" {
|
||||
return Some(255);
|
||||
}
|
||||
(self.0.field_id)(name)
|
||||
}
|
||||
|
||||
/// Extract the field name for the given field ID.
|
||||
pub fn field_name(&self, id: u8) -> Option<&'static str> {
|
||||
if id == 255 {
|
||||
return Some("label");
|
||||
}
|
||||
(self.0.field_name)(id)
|
||||
}
|
||||
|
||||
/// Extract the value of the field for the given field ID and style chain.
|
||||
pub fn field_from_styles(
|
||||
&self,
|
||||
id: u8,
|
||||
styles: StyleChain,
|
||||
) -> Result<Value, FieldAccessError> {
|
||||
(self.0.field_from_styles)(id, styles)
|
||||
}
|
||||
|
||||
/// 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))
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Element {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Element({})", self.name())
|
||||
}
|
||||
}
|
||||
|
||||
impl Repr for Element {
|
||||
fn repr(&self) -> EcoString {
|
||||
self.name().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Element {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.name().cmp(other.name())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Element {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
cast! {
|
||||
Element,
|
||||
self => Value::Func(self.into()),
|
||||
v: Func => v.element().ok_or("expected element")?,
|
||||
}
|
||||
|
||||
/// A Typst element that is defined by a native Rust type.
|
||||
pub trait NativeElement:
|
||||
Debug + Clone + Hash + Construct + Set + Capable + Send + Sync + 'static
|
||||
{
|
||||
const ELEMENT: TypedElementData<Self>;
|
||||
|
||||
/// Get the element data for the native Rust element.
|
||||
fn data() -> &'static NativeElementData
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Get the element for the native Rust element.
|
||||
fn elem() -> Element
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Element::from(Self::data())
|
||||
}
|
||||
|
||||
/// Pack the element into type-erased content.
|
||||
fn pack(self) -> Content
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Content::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NativeElement> IntoValue for T {
|
||||
fn into_value(self) -> Value {
|
||||
Value::Content(self.pack())
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to cast an element to a trait object for a trait it implements.
|
||||
///
|
||||
/// # 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<()>>;
|
||||
}
|
||||
|
||||
/// An element's constructor function.
|
||||
pub trait Construct {
|
||||
/// Construct an element from the arguments.
|
||||
///
|
||||
/// This is passed only the arguments that remain after execution of the
|
||||
/// element's set rule.
|
||||
fn construct(engine: &mut Engine, args: &mut Args) -> SourceResult<Content>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// An element's set rule.
|
||||
pub trait Set {
|
||||
/// Parse relevant arguments into style properties for this element.
|
||||
fn set(engine: &mut Engine, args: &mut Args) -> SourceResult<Styles>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// Synthesize fields on an element. This happens before execution of any show
|
||||
/// rule.
|
||||
pub trait Synthesize {
|
||||
/// Prepare the element for show rule application.
|
||||
fn synthesize(&mut self, engine: &mut Engine, styles: StyleChain)
|
||||
-> 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
|
||||
/// access the element's fields.
|
||||
pub trait ShowSet {
|
||||
/// Finalize the fully realized form of the element. Use this for effects
|
||||
/// that should work even in the face of a user-defined show rule.
|
||||
fn show_set(&self, styles: StyleChain) -> Styles;
|
||||
}
|
||||
|
||||
/// Type-erased metadata and routines for 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.
|
||||
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>,
|
||||
/// Associated definitions of the element.
|
||||
pub scope: LazyLock<Scope>,
|
||||
/// A list of parameter information for each field.
|
||||
pub params: LazyLock<Vec<ParamInfo>>,
|
||||
}
|
||||
|
||||
impl NativeElementData {
|
||||
/// Creates type-erased element data for the given element.
|
||||
pub const fn new<E>() -> Self
|
||||
where
|
||||
E: NativeElement,
|
||||
{
|
||||
Self {
|
||||
name: E::ELEMENT.name,
|
||||
title: E::ELEMENT.title,
|
||||
docs: E::ELEMENT.docs,
|
||||
keywords: E::ELEMENT.keywords,
|
||||
local_name: E::ELEMENT.local_name,
|
||||
field_from_styles: |i, styles| match E::ELEMENT.get(i) {
|
||||
Some(field) => {
|
||||
(field.get_from_styles)(styles).ok_or(FieldAccessError::Unknown)
|
||||
}
|
||||
None => Err(FieldAccessError::Unknown),
|
||||
},
|
||||
field_id: E::ELEMENT.field_id,
|
||||
field_name: |i| E::ELEMENT.get(i).map(|data| data.name),
|
||||
construct: <E as Construct>::construct,
|
||||
set: <E as Set>::set,
|
||||
vtable: <E as Capable>::vtable,
|
||||
scope: LazyLock::new(E::ELEMENT.scope),
|
||||
params: LazyLock::new(|| {
|
||||
E::ELEMENT
|
||||
.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()
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static NativeElementData> for Element {
|
||||
fn from(data: &'static NativeElementData) -> Self {
|
||||
Self(Static(data))
|
||||
}
|
||||
}
|
||||
|
||||
cast! {
|
||||
&'static NativeElementData,
|
||||
self => Element::from(self).into_value(),
|
||||
}
|
||||
|
||||
/// Type-aware metadata and routines for a native element.
|
||||
pub struct TypedElementData<E: NativeElement> {
|
||||
pub name: &'static str,
|
||||
pub title: &'static str,
|
||||
pub docs: &'static str,
|
||||
pub keywords: &'static [&'static str],
|
||||
pub fields: &'static [TypedFieldData<E>],
|
||||
pub field_id: fn(name: &str) -> Option<u8>,
|
||||
pub repr: Option<fn(&E) -> EcoString>,
|
||||
pub eq: Option<fn(&E, &E) -> bool>,
|
||||
pub local_name: Option<fn(Lang, Option<Region>) -> &'static str>,
|
||||
pub scope: fn() -> Scope,
|
||||
}
|
||||
|
||||
impl<E: NativeElement> TypedElementData<E> {
|
||||
/// Creates the data from its parts. This is called in the `#[elem]` macro.
|
||||
pub const fn new(
|
||||
name: &'static str,
|
||||
title: &'static str,
|
||||
docs: &'static str,
|
||||
fields: &'static [TypedFieldData<E>],
|
||||
field_id: fn(name: &str) -> Option<u8>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
title,
|
||||
docs,
|
||||
keywords: &[],
|
||||
fields,
|
||||
field_id,
|
||||
repr: None,
|
||||
eq: None,
|
||||
local_name: None,
|
||||
scope: || Scope::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Attaches search keywords for the documentation.
|
||||
pub const fn with_keywords(self, keywords: &'static [&'static str]) -> Self {
|
||||
Self { keywords, ..self }
|
||||
}
|
||||
|
||||
/// Takes a `Repr` impl into account.
|
||||
pub const fn with_repr(self) -> Self
|
||||
where
|
||||
E: Repr,
|
||||
{
|
||||
Self { repr: Some(E::repr), ..self }
|
||||
}
|
||||
|
||||
/// Takes a `PartialEq` impl into account.
|
||||
pub const fn with_partial_eq(self) -> Self
|
||||
where
|
||||
E: PartialEq,
|
||||
{
|
||||
Self { eq: Some(E::eq), ..self }
|
||||
}
|
||||
|
||||
/// Takes a `LocalName` impl into account.
|
||||
pub const fn with_local_name(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(self) -> Self
|
||||
where
|
||||
E: NativeScope,
|
||||
{
|
||||
Self { scope: || E::scope(), ..self }
|
||||
}
|
||||
|
||||
/// Retrieves the field with the given index.
|
||||
pub fn get(&self, i: u8) -> Option<&'static TypedFieldData<E>> {
|
||||
self.fields.get(usize::from(i))
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata for a field and routines that is aware of concrete element, but
|
||||
/// abstracts over what kind of field it is (required / variadic / synthesized /
|
||||
/// settable / ghost).
|
||||
pub struct TypedFieldData<E: NativeElement> {
|
||||
pub name: &'static str,
|
||||
pub docs: &'static str,
|
||||
pub positional: bool,
|
||||
pub variadic: bool,
|
||||
pub required: bool,
|
||||
pub settable: bool,
|
||||
pub synthesized: bool,
|
||||
pub input: fn() -> CastInfo,
|
||||
pub default: Option<fn() -> Value>,
|
||||
pub has: fn(content: &E) -> bool,
|
||||
pub get: fn(content: &E) -> Option<Value>,
|
||||
pub get_with_styles: fn(content: &E, StyleChain) -> Option<Value>,
|
||||
pub get_from_styles: fn(StyleChain) -> Option<Value>,
|
||||
pub materialize: fn(content: &mut E, styles: StyleChain),
|
||||
pub eq: fn(a: &E, b: &E) -> bool,
|
||||
}
|
||||
|
||||
impl<E: NativeElement> TypedFieldData<E> {
|
||||
/// Creates type-erased metadata and routines for a `#[required]` field.
|
||||
pub const fn required<const I: u8>() -> Self
|
||||
where
|
||||
E: RequiredField<I>,
|
||||
E::Type: Reflect + IntoValue + PartialEq,
|
||||
{
|
||||
Self {
|
||||
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 type-erased metadata and routines for a `#[variadic]` field.
|
||||
pub const fn variadic<const I: u8>() -> Self
|
||||
where
|
||||
E: RequiredField<I>,
|
||||
E::Type: Container + IntoValue + PartialEq,
|
||||
<E::Type as Container>::Inner: Reflect,
|
||||
{
|
||||
Self {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates type-erased metadata and routines for a `#[synthesized]` field.
|
||||
pub const fn synthesized<const I: u8>() -> Self
|
||||
where
|
||||
E: SynthesizedField<I>,
|
||||
E::Type: Reflect + IntoValue + PartialEq,
|
||||
{
|
||||
Self {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates type-erased metadata and routines for a normal settable field.
|
||||
pub const fn settable<const I: u8>() -> Self
|
||||
where
|
||||
E: SettableField<I>,
|
||||
E::Type: Reflect + IntoValue + PartialEq,
|
||||
{
|
||||
Self {
|
||||
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::PROPERTY.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(styles).into_value())
|
||||
},
|
||||
get_from_styles: |styles| Some(styles.get::<E, I>(Field::new()).into_value()),
|
||||
materialize: |elem, styles| {
|
||||
if !(E::FIELD.get)(elem).is_set() {
|
||||
(E::FIELD.get_mut)(elem).set(styles.get::<E, I>(Field::new()));
|
||||
}
|
||||
},
|
||||
eq: |a, b| (E::FIELD.get)(a).as_option() == (E::FIELD.get)(b).as_option(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates type-erased metadata and routines for a `#[ghost]` field.
|
||||
pub const fn ghost<const I: u8>() -> Self
|
||||
where
|
||||
E: SettableProperty<I>,
|
||||
E::Type: Reflect + IntoValue + PartialEq,
|
||||
{
|
||||
Self {
|
||||
name: E::PROPERTY.name,
|
||||
docs: E::PROPERTY.docs,
|
||||
positional: E::PROPERTY.positional,
|
||||
required: false,
|
||||
variadic: false,
|
||||
settable: true,
|
||||
synthesized: false,
|
||||
input: || <E::Type as Reflect>::input(),
|
||||
default: Some(|| E::PROPERTY.default().into_value()),
|
||||
has: |_| false,
|
||||
get: |_| None,
|
||||
get_with_styles: |_, styles| {
|
||||
Some(styles.get::<E, I>(Field::new()).into_value())
|
||||
},
|
||||
get_from_styles: |styles| Some(styles.get::<E, I>(Field::new()).into_value()),
|
||||
materialize: |_, _| {},
|
||||
eq: |_, _| true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates type-erased metadata and routines for an `#[external]` field.
|
||||
pub const fn external<const I: u8>() -> Self
|
||||
where
|
||||
E: ExternalField<I>,
|
||||
E::Type: Reflect + IntoValue,
|
||||
{
|
||||
Self {
|
||||
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 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> {
|
||||
pub name: &'static str,
|
||||
pub docs: &'static str,
|
||||
pub get: fn(&E) -> &E::Type,
|
||||
pub get_mut: fn(&mut E) -> &mut 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,
|
||||
get_mut: fn(&mut E) -> &mut E::Type,
|
||||
) -> Self {
|
||||
Self { name, docs, get, get_mut }
|
||||
}
|
||||
}
|
||||
|
||||
/// A field that is initially unset, but may be set through a [`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> {
|
||||
pub name: &'static str,
|
||||
pub docs: &'static str,
|
||||
pub get: fn(&E) -> &Option<E::Type>,
|
||||
pub get_mut: fn(&mut E) -> &mut 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>,
|
||||
get_mut: fn(&mut E) -> &mut Option<E::Type>,
|
||||
) -> Self {
|
||||
Self { name, docs, get, get_mut }
|
||||
}
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
pub get: fn(&E) -> &Settable<E, I>,
|
||||
pub get_mut: fn(&mut E) -> &mut Settable<E, I>,
|
||||
pub 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
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 PROPERTY: SettablePropertyData<Self, I>;
|
||||
}
|
||||
|
||||
impl<T, const I: u8> SettableProperty<I> for T
|
||||
where
|
||||
T: SettableField<I>,
|
||||
{
|
||||
type Type = <Self as SettableField<I>>::Type;
|
||||
|
||||
const PROPERTY: SettablePropertyData<Self, I> =
|
||||
<Self as SettableField<I>>::FIELD.property;
|
||||
}
|
||||
|
||||
/// Metadata and routines for a [`SettableProperty`].
|
||||
pub struct SettablePropertyData<E: SettableProperty<I>, const I: u8> {
|
||||
pub name: &'static str,
|
||||
pub docs: &'static str,
|
||||
pub positional: bool,
|
||||
pub default: fn() -> E::Type,
|
||||
pub slot: fn() -> &'static OnceLock<E::Type>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fold: Option<fn(E::Type, E::Type) -> 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 }
|
||||
}
|
||||
|
||||
/// Produces an instance of the property's default value.
|
||||
pub fn default(&self) -> E::Type
|
||||
where
|
||||
E::Type: Clone,
|
||||
{
|
||||
// Avoid recreating an expensive instance over and over, but also
|
||||
// avoid unnecessary lazy initialization for cheap types.
|
||||
if std::mem::needs_drop::<E::Type>() {
|
||||
self.default_ref().clone()
|
||||
} else {
|
||||
(self.default)()
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces a static reference to this property's default value.
|
||||
pub fn default_ref(&self) -> &'static E::Type {
|
||||
(self.slot)().get_or_init(self.default)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[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 or if it needs folding.
|
||||
pub fn get<'a>(&'a self, styles: StyleChain<'a>) -> E::Type
|
||||
where
|
||||
E::Type: Clone,
|
||||
{
|
||||
styles.get_with::<E, I>(Field::new(), self.0.as_ref())
|
||||
}
|
||||
|
||||
/// Retrieves a reference to the value given styles. Not possible if the
|
||||
/// value needs folding.
|
||||
pub fn get_ref<'a>(&'a self, styles: StyleChain<'a>) -> &'a E::Type
|
||||
where
|
||||
E: RefableProperty<I>,
|
||||
{
|
||||
styles.get_ref_with::<E, I>(Field::new(), self.0.as_ref())
|
||||
}
|
||||
|
||||
/// 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 + Clone,
|
||||
{
|
||||
self.get(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 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`] 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 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> {
|
||||
pub name: &'static str,
|
||||
pub docs: &'static str,
|
||||
pub 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 }
|
||||
}
|
||||
}
|
@ -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::*;
|
||||
|
@ -352,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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -540,28 +543,50 @@ impl<'a> StyleChain<'a> {
|
||||
Self { head: &root.0, tail: None }
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// Retrieves the value of the given field from the style chain.
|
||||
///
|
||||
/// 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
|
||||
E: SettableProperty<I>,
|
||||
E::Type: Copy,
|
||||
{
|
||||
self.get_with(field, None)
|
||||
self.get_cloned(field)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
@ -573,83 +598,58 @@ impl<'a> StyleChain<'a> {
|
||||
E: SettableProperty<I>,
|
||||
E::Type: Resolve,
|
||||
{
|
||||
self.get(field).resolve(self)
|
||||
}
|
||||
|
||||
/// Retrieves a reference to the value of the given field from the style
|
||||
/// chain.
|
||||
pub fn get_ref<E, const I: u8>(self, field: Field<E, I>) -> &'a E::Type
|
||||
where
|
||||
E: RefableProperty<I>,
|
||||
{
|
||||
self.get_ref_with(field, None)
|
||||
}
|
||||
|
||||
/// Retrieves a field, also taking into account the instance's value if any.
|
||||
pub(crate) fn get_with<E, const I: u8>(
|
||||
self,
|
||||
_: Field<E, I>,
|
||||
inherent: Option<&'a E::Type>,
|
||||
) -> E::Type
|
||||
where
|
||||
E: SettableProperty<I>,
|
||||
{
|
||||
let mut properties = self.properties::<E::Type>(E::elem(), I, inherent);
|
||||
if let Some(fold) = E::PROPERTY.fold {
|
||||
fn next<T>(
|
||||
mut values: impl Iterator<Item = T>,
|
||||
default: fn() -> T,
|
||||
fold: fn(T, T) -> T,
|
||||
) -> T {
|
||||
values
|
||||
.next()
|
||||
.map(|value| fold(value, next(values, default, fold)))
|
||||
.unwrap_or_else(default)
|
||||
}
|
||||
next(properties.cloned(), || E::PROPERTY.default(), fold)
|
||||
} else {
|
||||
properties.next().cloned().unwrap_or_else(|| E::PROPERTY.default())
|
||||
}
|
||||
self.get_cloned(field).resolve(self)
|
||||
}
|
||||
|
||||
/// Retrieves a reference to a field, also taking into account the
|
||||
/// instance's value if any.
|
||||
pub(crate) fn get_ref_with<E, const I: u8>(
|
||||
self,
|
||||
_: Field<E, I>,
|
||||
inherent: Option<&'a E::Type>,
|
||||
) -> &'a E::Type
|
||||
where
|
||||
E: RefableProperty<I>,
|
||||
{
|
||||
self.properties::<E::Type>(E::elem(), I, inherent)
|
||||
.next()
|
||||
.unwrap_or_else(|| E::PROPERTY.default_ref())
|
||||
fn get_unfolded<T: 'static>(self, func: Element, id: u8) -> Option<&'a T> {
|
||||
self.find(func, id).map(|block| block.downcast(func, id))
|
||||
}
|
||||
|
||||
/// Iterate over all values for the given property in the chain.
|
||||
fn properties<T: '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<&'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
|
||||
)
|
||||
})
|
||||
}),
|
||||
)
|
||||
fold: fn(T, T) -> T,
|
||||
default: T,
|
||||
) -> T {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over all values for the given property in the chain.
|
||||
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.
|
||||
@ -842,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;
|
||||
@ -885,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
|
||||
@ -922,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
|
||||
)
|
||||
}
|
||||
|
@ -249,7 +249,6 @@ fn create(element: &Elem) -> Result<TokenStream> {
|
||||
let native_element_impl = create_native_elem_impl(element);
|
||||
let field_impls =
|
||||
element.fields.iter().map(|field| create_field_impl(element, field));
|
||||
let capable_impl = create_capable_impl(element);
|
||||
let construct_impl =
|
||||
element.cannot("Construct").then(|| create_construct_impl(element));
|
||||
let set_impl = element.cannot("Set").then(|| create_set_impl(element));
|
||||
@ -265,7 +264,6 @@ fn create(element: &Elem) -> Result<TokenStream> {
|
||||
#inherent_impl
|
||||
#native_element_impl
|
||||
#(#field_impls)*
|
||||
#capable_impl
|
||||
#construct_impl
|
||||
#set_impl
|
||||
#locatable_impl
|
||||
@ -365,9 +363,9 @@ fn create_with_field_method(field: &Field) -> TokenStream {
|
||||
let expr = if field.required {
|
||||
quote! { self.#ident = #ident }
|
||||
} else if field.synthesized {
|
||||
quote! { self.#ident = Some(#ident); }
|
||||
quote! { self.#ident = Some(#ident) }
|
||||
} else {
|
||||
quote! { self.#ident.set(#ident); }
|
||||
quote! { self.#ident.set(#ident) }
|
||||
};
|
||||
|
||||
quote! {
|
||||
@ -384,21 +382,20 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
||||
let Elem { name, ident, title, scope, keywords, docs, .. } = element;
|
||||
|
||||
let fields = element.fields.iter().filter(|field| !field.internal).map(|field| {
|
||||
let method = if field.external {
|
||||
quote! { external }
|
||||
} else if field.variadic {
|
||||
quote! { variadic }
|
||||
} else if field.required {
|
||||
quote! { required }
|
||||
} else if field.synthesized {
|
||||
quote! { synthesized }
|
||||
} else if field.ghost {
|
||||
quote! { ghost }
|
||||
} else {
|
||||
quote! { settable }
|
||||
};
|
||||
let i = field.i;
|
||||
quote! { #foundations::TypedFieldData::<Self>::#method::<#i>() }
|
||||
if field.external {
|
||||
quote! { #foundations::ExternalFieldData::<#ident, #i>::vtable() }
|
||||
} else if field.variadic {
|
||||
quote! { #foundations::RequiredFieldData::<#ident, #i>::vtable_variadic() }
|
||||
} else if field.required {
|
||||
quote! { #foundations::RequiredFieldData::<#ident, #i>::vtable() }
|
||||
} else if field.synthesized {
|
||||
quote! { #foundations::SynthesizedFieldData::<#ident, #i>::vtable() }
|
||||
} else if field.ghost {
|
||||
quote! { #foundations::SettablePropertyData::<#ident, #i>::vtable() }
|
||||
} else {
|
||||
quote! { #foundations::SettableFieldData::<#ident, #i>::vtable() }
|
||||
}
|
||||
});
|
||||
|
||||
let field_arms = element
|
||||
@ -416,6 +413,8 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
||||
}
|
||||
};
|
||||
|
||||
let capable_func = create_capable_func(element);
|
||||
|
||||
let with_keywords =
|
||||
(!keywords.is_empty()).then(|| quote! { .with_keywords(&[#(#keywords),*]) });
|
||||
let with_repr = element.can("Repr").then(|| quote! { .with_repr() });
|
||||
@ -424,25 +423,27 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
||||
let with_scope = scope.then(|| quote! { .with_scope() });
|
||||
|
||||
quote! {
|
||||
impl #foundations::NativeElement for #ident {
|
||||
const ELEMENT: #foundations::TypedElementData<Self> =
|
||||
#foundations::TypedElementData::new(
|
||||
#name,
|
||||
#title,
|
||||
#docs,
|
||||
&[#(#fields),*],
|
||||
#field_id,
|
||||
) #with_keywords
|
||||
#with_repr
|
||||
#with_partial_eq
|
||||
#with_local_name
|
||||
#with_scope;
|
||||
|
||||
fn data() -> &'static #foundations::NativeElementData {
|
||||
static DATA: #foundations::NativeElementData =
|
||||
#foundations::NativeElementData::new::<#ident>();
|
||||
&DATA
|
||||
}
|
||||
unsafe impl #foundations::NativeElement for #ident {
|
||||
const ELEM: #foundations::Element = #foundations::Element::from_vtable({
|
||||
static STORE: #foundations::LazyElementStore
|
||||
= #foundations::LazyElementStore::new();
|
||||
static VTABLE: #foundations::ContentVtable =
|
||||
#foundations::ContentVtable::new::<#ident>(
|
||||
#name,
|
||||
#title,
|
||||
#docs,
|
||||
&[#(#fields),*],
|
||||
#field_id,
|
||||
#capable_func,
|
||||
|| &STORE,
|
||||
) #with_keywords
|
||||
#with_repr
|
||||
#with_partial_eq
|
||||
#with_local_name
|
||||
#with_scope
|
||||
.erase();
|
||||
&VTABLE
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -457,11 +458,6 @@ fn create_field_impl(element: &Elem, field: &Field) -> TokenStream {
|
||||
None => quote! { std::default::Default::default },
|
||||
};
|
||||
|
||||
let getters = quote! {
|
||||
|elem| &elem.#ident,
|
||||
|elem| &mut elem.#ident
|
||||
};
|
||||
|
||||
if field.external {
|
||||
quote! {
|
||||
impl #foundations::ExternalField<#i> for #elem_ident {
|
||||
@ -482,7 +478,7 @@ fn create_field_impl(element: &Elem, field: &Field) -> TokenStream {
|
||||
#foundations::RequiredFieldData::<Self, #i>::new(
|
||||
#name,
|
||||
#docs,
|
||||
#getters,
|
||||
|elem| &elem.#ident,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -494,7 +490,7 @@ fn create_field_impl(element: &Elem, field: &Field) -> TokenStream {
|
||||
#foundations::SynthesizedFieldData::<Self, #i>::new(
|
||||
#name,
|
||||
#docs,
|
||||
#getters,
|
||||
|elem| &elem.#ident,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -517,7 +513,7 @@ fn create_field_impl(element: &Elem, field: &Field) -> TokenStream {
|
||||
quote! {
|
||||
impl #foundations::SettableProperty<#i> for #elem_ident {
|
||||
type Type = #ty;
|
||||
const PROPERTY: #foundations::SettablePropertyData<Self, #i> =
|
||||
const FIELD: #foundations::SettablePropertyData<Self, #i> =
|
||||
#foundations::SettablePropertyData::<Self, #i>::new(
|
||||
#name,
|
||||
#docs,
|
||||
@ -537,7 +533,8 @@ fn create_field_impl(element: &Elem, field: &Field) -> TokenStream {
|
||||
#name,
|
||||
#docs,
|
||||
#positional,
|
||||
#getters,
|
||||
|elem| &elem.#ident,
|
||||
|elem| &mut elem.#ident,
|
||||
#default,
|
||||
#slot,
|
||||
) #with_fold;
|
||||
@ -633,7 +630,7 @@ fn create_field_parser(field: &Field) -> (TokenStream, TokenStream) {
|
||||
}
|
||||
|
||||
/// Creates the element's casting vtable.
|
||||
fn create_capable_impl(element: &Elem) -> TokenStream {
|
||||
fn create_capable_func(element: &Elem) -> TokenStream {
|
||||
// Forbidden capabilities (i.e capabilities that are not object safe).
|
||||
const FORBIDDEN: &[&str] =
|
||||
&["Debug", "PartialEq", "Hash", "Construct", "Set", "Repr", "LocalName"];
|
||||
@ -657,12 +654,10 @@ fn create_capable_impl(element: &Elem) -> TokenStream {
|
||||
});
|
||||
|
||||
quote! {
|
||||
unsafe impl #foundations::Capable for #ident {
|
||||
fn vtable(capability: ::std::any::TypeId) -> ::std::option::Option<::std::ptr::NonNull<()>> {
|
||||
let dangling = ::std::ptr::NonNull::<#foundations::Packed<#ident>>::dangling().as_ptr();
|
||||
#(#checks)*
|
||||
None
|
||||
}
|
||||
|capability| {
|
||||
let dangling = ::std::ptr::NonNull::<#foundations::Packed<#ident>>::dangling().as_ptr();
|
||||
#(#checks)*
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user