mirror of
https://github.com/typst/typst
synced 2025-07-27 22:37:54 +08:00
565 lines
17 KiB
Rust
565 lines
17 KiB
Rust
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
|
|
}
|
|
}
|