summaryrefslogtreecommitdiff
path: root/crates/typst-library/src/foundations/content.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-library/src/foundations/content.rs')
-rw-r--r--crates/typst-library/src/foundations/content.rs1007
1 files changed, 1007 insertions, 0 deletions
diff --git a/crates/typst-library/src/foundations/content.rs b/crates/typst-library/src/foundations/content.rs
new file mode 100644
index 00000000..a274b8bf
--- /dev/null
+++ b/crates/typst-library/src/foundations/content.rs
@@ -0,0 +1,1007 @@
+use std::any::TypeId;
+use std::fmt::{self, Debug, Formatter};
+use std::hash::{Hash, Hasher};
+use std::iter::{self, Sum};
+use std::marker::PhantomData;
+use std::ops::{Add, AddAssign, Deref, DerefMut};
+use std::sync::Arc;
+
+use comemo::Tracked;
+use ecow::{eco_format, EcoString};
+use serde::{Serialize, Serializer};
+use smallvec::smallvec;
+use typst_syntax::Span;
+use typst_utils::{fat, singleton, LazyHash, SmallBitSet};
+
+use crate::diag::{SourceResult, StrResult};
+use crate::engine::Engine;
+use crate::foundations::{
+ elem, func, scope, ty, Context, Dict, Element, Fields, IntoValue, Label,
+ NativeElement, Recipe, RecipeIndex, Repr, Selector, Str, Style, StyleChain, Styles,
+ Value,
+};
+use crate::introspection::Location;
+use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides};
+use crate::model::{Destination, EmphElem, LinkElem, StrongElem};
+use crate::text::UnderlineElem;
+
+/// A piece of document content.
+///
+/// This type is at the heart of Typst. All markup you write and most
+/// [functions]($function) you call produce content values. You can create a
+/// content value by enclosing markup in square brackets. This is also how you
+/// pass content to functions.
+///
+/// # Example
+/// ```example
+/// Type of *Hello!* is
+/// #type([*Hello!*])
+/// ```
+///
+/// Content can be added with the `+` operator,
+/// [joined together]($scripting/#blocks) and multiplied with integers. Wherever
+/// content is expected, you can also pass a [string]($str) or `{none}`.
+///
+/// # Representation
+/// Content consists of elements with fields. When constructing an element with
+/// its _element function,_ you provide these fields as arguments and when you
+/// have a content value, you can access its fields with [field access
+/// syntax]($scripting/#field-access).
+///
+/// Some fields are required: These must be provided when constructing an
+/// element and as a consequence, they are always available through field access
+/// on content of that type. Required fields are marked as such in the
+/// documentation.
+///
+/// Most fields are optional: Like required fields, they can be passed to the
+/// element function to configure them for a single element. However, these can
+/// also be configured with [set rules]($styling/#set-rules) to apply them to
+/// all elements within a scope. Optional fields are only available with field
+/// access syntax when they were explicitly passed to the element function, not
+/// when they result from a set rule.
+///
+/// Each element has a default appearance. However, you can also completely
+/// customize its appearance with a [show rule]($styling/#show-rules). The show
+/// rule is passed the element. It can access the element's field and produce
+/// arbitrary content from it.
+///
+/// In the web app, you can hover over a content variable to see exactly which
+/// 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>,
+}
+
+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(),
+ }
+ }
+
+ /// Creates a empty sequence content.
+ pub fn empty() -> Self {
+ singleton!(Content, SequenceElem::default().pack()).clone()
+ }
+
+ /// Get the element of this content.
+ pub fn elem(&self) -> Element {
+ self.inner.elem.dyn_elem()
+ }
+
+ /// Get the span of the content.
+ pub fn span(&self) -> Span {
+ self.span
+ }
+
+ /// Set the span of the content.
+ pub fn spanned(mut self, span: Span) -> Self {
+ if self.span.is_detached() {
+ self.span = span;
+ }
+ self
+ }
+
+ /// Get the label of the content.
+ pub fn label(&self) -> Option<Label> {
+ self.inner.label
+ }
+
+ /// Attach a label to the content.
+ pub fn labelled(mut self, label: Label) -> Self {
+ self.set_label(label);
+ self
+ }
+
+ /// Set the label of the content.
+ pub fn set_label(&mut self, label: Label) {
+ self.make_mut().label = Some(label);
+ }
+
+ /// Assigns a location to the content.
+ ///
+ /// This identifies the content and e.g. makes it linkable by
+ /// `.linked(Destination::Location(loc))`.
+ ///
+ /// Useful in combination with [`Location::variant`].
+ pub fn located(mut self, loc: Location) -> Self {
+ self.set_location(loc);
+ self
+ }
+
+ /// Set the location of the content.
+ pub fn set_location(&mut self, location: Location) {
+ self.make_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)
+ }
+
+ /// Disable a show rule recipe.
+ pub fn guarded(mut self, index: RecipeIndex) -> Self {
+ self.make_mut().lifecycle.insert(index.0);
+ self
+ }
+
+ /// Whether this content has already been prepared.
+ pub fn is_prepared(&self) -> bool {
+ self.inner.lifecycle.contains(0)
+ }
+
+ /// Mark this content as prepared.
+ pub fn mark_prepared(&mut self) {
+ self.make_mut().lifecycle.insert(0);
+ }
+
+ /// Get a field by ID.
+ ///
+ /// This is the preferred way to access fields. However, you can only use it
+ /// if you have set the field IDs yourself or are using the field IDs
+ /// generated by the `#[elem]` macro.
+ pub fn get(
+ &self,
+ id: u8,
+ styles: Option<StyleChain>,
+ ) -> Result<Value, FieldAccessError> {
+ if id == 255 {
+ if let Some(label) = self.label() {
+ return Ok(label.into_value());
+ }
+ }
+ match styles {
+ Some(styles) => self.inner.elem.field_with_styles(id, styles),
+ None => self.inner.elem.field(id),
+ }
+ }
+
+ /// Get a field by name.
+ ///
+ /// If you have access to the field IDs of the element, use [`Self::get`]
+ /// instead.
+ pub fn get_by_name(&self, name: &str) -> Result<Value, FieldAccessError> {
+ if name == "label" {
+ if let Some(label) = self.label() {
+ return Ok(label.into_value());
+ }
+ }
+ let id = self.elem().field_id(name).ok_or(FieldAccessError::Unknown)?;
+ self.get(id, None)
+ }
+
+ /// Get a field by ID, returning a missing field error if it does not exist.
+ ///
+ /// This is the preferred way to access fields. However, you can only use it
+ /// if you have set the field IDs yourself or are using the field IDs
+ /// generated by the `#[elem]` macro.
+ pub fn field(&self, id: u8) -> StrResult<Value> {
+ self.get(id, None)
+ .map_err(|e| e.message(self, self.elem().field_name(id).unwrap()))
+ }
+
+ /// Get a field by name, returning a missing field error if it does not
+ /// exist.
+ ///
+ /// If you have access to the field IDs of the element, use [`Self::field`]
+ /// instead.
+ pub fn field_by_name(&self, name: &str) -> StrResult<Value> {
+ self.get_by_name(name).map_err(|e| e.message(self, name))
+ }
+
+ /// Resolve all fields with the styles and save them in-place.
+ pub fn materialize(&mut self, styles: StyleChain) {
+ self.make_mut().elem.materialize(styles);
+ }
+
+ /// Create a new sequence element from multiples elements.
+ pub fn sequence(iter: impl IntoIterator<Item = Self>) -> Self {
+ let vec: Vec<_> = iter.into_iter().collect();
+ if vec.is_empty() {
+ Self::empty()
+ } else if vec.len() == 1 {
+ vec.into_iter().next().unwrap()
+ } else {
+ SequenceElem::new(vec).into()
+ }
+ }
+
+ /// Whether the contained element is of type `T`.
+ pub fn is<T: NativeElement>(&self) -> bool {
+ self.inner.elem.dyn_type_id() == TypeId::of::<T>()
+ }
+
+ /// Downcasts the element to a packed value.
+ pub fn to_packed<T: NativeElement>(&self) -> Option<&Packed<T>> {
+ Packed::from_ref(self)
+ }
+
+ /// Downcasts the element to a mutable packed value.
+ pub fn to_packed_mut<T: NativeElement>(&mut self) -> Option<&mut Packed<T>> {
+ Packed::from_mut(self)
+ }
+
+ /// Downcasts the element into an owned packed value.
+ pub fn into_packed<T: NativeElement>(self) -> Result<Packed<T>, Self> {
+ Packed::from_owned(self)
+ }
+
+ /// Extract the raw underlying element.
+ pub fn unpack<T: NativeElement>(self) -> Result<T, Self> {
+ 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
+ C: ?Sized + 'static,
+ {
+ self.elem().can::<C>()
+ }
+
+ /// Cast to a trait object if the contained element has the given
+ /// capability.
+ pub 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 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()) })
+ }
+
+ /// Cast to a mutable trait object if the contained element has the given
+ /// capability.
+ pub 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 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()) })
+ }
+
+ /// Whether the content is an empty sequence.
+ pub fn is_empty(&self) -> bool {
+ let Some(sequence) = self.to_packed::<SequenceElem>() else {
+ return false;
+ };
+
+ sequence.children.is_empty()
+ }
+
+ /// Also auto expands sequence of sequences into flat sequence
+ pub fn sequence_recursive_for_each<'a>(&'a self, f: &mut impl FnMut(&'a Self)) {
+ if let Some(sequence) = self.to_packed::<SequenceElem>() {
+ for child in &sequence.children {
+ child.sequence_recursive_for_each(f);
+ }
+ } else {
+ f(self);
+ }
+ }
+
+ /// Style this content with a recipe, eagerly applying it if possible.
+ pub fn styled_with_recipe(
+ self,
+ engine: &mut Engine,
+ context: Tracked<Context>,
+ recipe: Recipe,
+ ) -> SourceResult<Self> {
+ if recipe.selector().is_none() {
+ recipe.apply(engine, context, self)
+ } else {
+ Ok(self.styled(recipe))
+ }
+ }
+
+ /// Repeat this content `count` times.
+ pub fn repeat(&self, count: usize) -> Self {
+ Self::sequence(std::iter::repeat_with(|| self.clone()).take(count))
+ }
+
+ /// Style this content with a style entry.
+ pub fn styled(mut self, style: impl Into<Style>) -> Self {
+ if let Some(style_elem) = self.to_packed_mut::<StyledElem>() {
+ style_elem.styles.apply_one(style.into());
+ self
+ } else {
+ self.styled_with_map(style.into().into())
+ }
+ }
+
+ /// Style this content with a full style map.
+ pub fn styled_with_map(mut self, styles: Styles) -> Self {
+ if styles.is_empty() {
+ return self;
+ }
+
+ if let Some(style_elem) = self.to_packed_mut::<StyledElem>() {
+ style_elem.styles.apply(styles);
+ self
+ } else {
+ StyledElem::new(self, styles).into()
+ }
+ }
+
+ /// Style this content with a full style map in-place.
+ pub fn style_in_place(&mut self, styles: Styles) {
+ if styles.is_empty() {
+ return;
+ }
+
+ if let Some(style_elem) = self.to_packed_mut::<StyledElem>() {
+ style_elem.styles.apply(styles);
+ } else {
+ *self = StyledElem::new(std::mem::take(self), styles).into();
+ }
+ }
+
+ /// Queries the content tree for all elements that match the given selector.
+ ///
+ /// Elements produced in `show` rules will not be included in the results.
+ pub fn query(&self, selector: Selector) -> Vec<Content> {
+ let mut results = Vec::new();
+ self.traverse(&mut |element| {
+ if selector.matches(&element, None) {
+ results.push(element);
+ }
+ });
+ results
+ }
+
+ /// Queries the content tree for the first element that match the given
+ /// selector.
+ ///
+ /// Elements produced in `show` rules will not be included in the results.
+ pub fn query_first(&self, selector: Selector) -> Option<Content> {
+ let mut result = None;
+ self.traverse(&mut |element| {
+ if result.is_none() && selector.matches(&element, None) {
+ result = Some(element);
+ }
+ });
+ result
+ }
+
+ /// Extracts the plain text of this content.
+ pub fn plain_text(&self) -> EcoString {
+ let mut text = EcoString::new();
+ self.traverse(&mut |element| {
+ if let Some(textable) = element.with::<dyn PlainText>() {
+ textable.plain_text(&mut text);
+ }
+ });
+ text
+ }
+
+ /// Traverse this content.
+ fn traverse<F>(&self, f: &mut F)
+ where
+ F: FnMut(Content),
+ {
+ f(self.clone());
+
+ self.inner
+ .elem
+ .fields()
+ .into_iter()
+ .for_each(|(_, value)| walk_value(value, f));
+
+ /// Walks a given value to find any content that matches the selector.
+ fn walk_value<F>(value: Value, f: &mut F)
+ where
+ F: FnMut(Content),
+ {
+ match value {
+ Value::Content(content) => content.traverse(f),
+ Value::Array(array) => {
+ for value in array {
+ walk_value(value, f);
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+}
+
+impl Content {
+ /// Strongly emphasize this content.
+ pub fn strong(self) -> Self {
+ StrongElem::new(self).pack()
+ }
+
+ /// Emphasize this content.
+ pub fn emph(self) -> Self {
+ EmphElem::new(self).pack()
+ }
+
+ /// Underline this content.
+ pub fn underlined(self) -> Self {
+ UnderlineElem::new(self).pack()
+ }
+
+ /// Link the content somewhere.
+ pub fn linked(self, dest: Destination) -> Self {
+ self.styled(LinkElem::set_dests(smallvec![dest]))
+ }
+
+ /// Set alignments for this content.
+ pub fn aligned(self, align: Alignment) -> Self {
+ self.styled(AlignElem::set_alignment(align))
+ }
+
+ /// Pad this content at the sides.
+ pub fn padded(self, padding: Sides<Rel<Length>>) -> Self {
+ PadElem::new(self)
+ .with_left(padding.left)
+ .with_top(padding.top)
+ .with_right(padding.right)
+ .with_bottom(padding.bottom)
+ .pack()
+ }
+
+ /// Transform this content's contents without affecting layout.
+ pub fn moved(self, delta: Axes<Rel<Length>>) -> Self {
+ MoveElem::new(self).with_dx(delta.x).with_dy(delta.y).pack()
+ }
+}
+
+#[scope]
+impl Content {
+ /// The content's element function. This function can be used to create the element
+ /// contained in this content. It can be used in set and show rules for the
+ /// element. Can be compared with global functions to check whether you have
+ /// a specific
+ /// kind of element.
+ #[func]
+ pub fn func(&self) -> Element {
+ self.elem()
+ }
+
+ /// Whether the content has the specified field.
+ #[func]
+ pub fn has(
+ &self,
+ /// The field to look for.
+ field: Str,
+ ) -> bool {
+ if field.as_str() == "label" {
+ return self.label().is_some();
+ }
+
+ let Some(id) = self.elem().field_id(&field) else {
+ return false;
+ };
+
+ self.inner.elem.has(id)
+ }
+
+ /// Access the specified field on the content. Returns the default value if
+ /// the field does not exist or fails with an error if no default value was
+ /// specified.
+ #[func]
+ pub fn at(
+ &self,
+ /// The field to access.
+ field: Str,
+ /// A default value to return if the field does not exist.
+ #[named]
+ default: Option<Value>,
+ ) -> StrResult<Value> {
+ self.get_by_name(&field)
+ .or_else(|e| default.ok_or(e))
+ .map_err(|e| e.message_no_default(self, &field))
+ }
+
+ /// Returns the fields of this content.
+ ///
+ /// ```example
+ /// #rect(
+ /// width: 10cm,
+ /// height: 10cm,
+ /// ).fields()
+ /// ```
+ #[func]
+ pub fn fields(&self) -> Dict {
+ let mut dict = self.inner.elem.fields();
+ if let Some(label) = self.label() {
+ dict.insert("label".into(), label.into_value());
+ }
+ dict
+ }
+
+ /// The location of the content. This is only available on content returned
+ /// by [query] or provided by a [show rule]($reference/styling/#show-rules),
+ /// for other content it will be `{none}`. The resulting location can be
+ /// used with [counters]($counter), [state] and [queries]($query).
+ #[func]
+ pub fn location(&self) -> Option<Location> {
+ self.inner.location
+ }
+}
+
+impl Default for Content {
+ fn default() -> Self {
+ Self::empty()
+ }
+}
+
+impl Debug for Content {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ self.inner.elem.fmt(f)
+ }
+}
+
+impl<T: NativeElement> From<T> for Content {
+ fn from(value: T) -> Self {
+ Self::new(value)
+ }
+}
+
+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()
+ }
+}
+
+impl Add for Content {
+ type Output = Self;
+
+ fn add(self, mut rhs: Self) -> Self::Output {
+ let mut lhs = self;
+ match (lhs.to_packed_mut::<SequenceElem>(), rhs.to_packed_mut::<SequenceElem>()) {
+ (Some(seq_lhs), Some(rhs)) => {
+ seq_lhs.children.extend(rhs.children.iter().cloned());
+ lhs
+ }
+ (Some(seq_lhs), None) => {
+ seq_lhs.children.push(rhs);
+ lhs
+ }
+ (None, Some(rhs_seq)) => {
+ rhs_seq.children.insert(0, lhs);
+ rhs
+ }
+ (None, None) => Self::sequence([lhs, rhs]),
+ }
+ }
+}
+
+impl<'a> Add<&'a Self> for Content {
+ type Output = Self;
+
+ fn add(self, rhs: &'a Self) -> Self::Output {
+ let mut lhs = self;
+ match (lhs.to_packed_mut::<SequenceElem>(), rhs.to_packed::<SequenceElem>()) {
+ (Some(seq_lhs), Some(rhs)) => {
+ seq_lhs.children.extend(rhs.children.iter().cloned());
+ lhs
+ }
+ (Some(seq_lhs), None) => {
+ seq_lhs.children.push(rhs.clone());
+ lhs
+ }
+ (None, Some(_)) => {
+ let mut rhs = rhs.clone();
+ rhs.to_packed_mut::<SequenceElem>().unwrap().children.insert(0, lhs);
+ rhs
+ }
+ (None, None) => Self::sequence([lhs, rhs.clone()]),
+ }
+ }
+}
+
+impl AddAssign for Content {
+ fn add_assign(&mut self, rhs: Self) {
+ *self = std::mem::take(self) + rhs;
+ }
+}
+
+impl AddAssign<&Self> for Content {
+ fn add_assign(&mut self, rhs: &Self) {
+ *self = std::mem::take(self) + rhs;
+ }
+}
+
+impl Sum for Content {
+ fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
+ Self::sequence(iter)
+ }
+}
+
+impl Serialize for Content {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ serializer.collect_map(
+ iter::once(("func".into(), self.func().name().into_value()))
+ .chain(self.fields()),
+ )
+ }
+}
+
+/// The trait that combines all the other traits into a trait object.
+trait Bounds: Debug + Repr + Fields + Send + Sync + 'static {
+ fn dyn_type_id(&self) -> TypeId;
+ fn dyn_elem(&self) -> Element;
+ fn dyn_clone(&self, inner: &Inner<dyn Bounds>, span: Span) -> Content;
+ fn dyn_hash(&self, hasher: &mut dyn Hasher);
+ fn dyn_eq(&self, other: &Content) -> bool;
+}
+
+impl<T: NativeElement> Bounds for T {
+ fn dyn_type_id(&self) -> TypeId {
+ TypeId::of::<Self>()
+ }
+
+ fn dyn_elem(&self) -> Element {
+ Self::elem()
+ }
+
+ fn dyn_clone(&self, inner: &Inner<dyn Bounds>, span: Span) -> Content {
+ Content {
+ inner: Arc::new(Inner {
+ label: inner.label,
+ location: inner.location,
+ lifecycle: inner.lifecycle.clone(),
+ elem: LazyHash::reuse(self.clone(), &inner.elem),
+ }),
+ span,
+ }
+ }
+
+ fn dyn_hash(&self, mut state: &mut dyn Hasher) {
+ TypeId::of::<Self>().hash(&mut state);
+ self.hash(&mut state);
+ }
+
+ fn dyn_eq(&self, other: &Content) -> bool {
+ let Some(other) = other.to_packed::<Self>() else {
+ return false;
+ };
+ *self == **other
+ }
+}
+
+impl Hash for dyn Bounds {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.dyn_hash(state);
+ }
+}
+
+/// A packed element of a static type.
+#[derive(Clone, PartialEq, Hash)]
+#[repr(transparent)]
+pub struct Packed<T: NativeElement>(
+ /// Invariant: Must be of type `T`.
+ Content,
+ PhantomData<T>,
+);
+
+impl<T: NativeElement> Packed<T> {
+ /// Pack element while retaining its static type.
+ pub fn new(element: T) -> Self {
+ // Safety: The element is known to be of type `T`.
+ Packed(element.pack(), PhantomData)
+ }
+
+ /// Try to cast type-erased content into a statically known packed element.
+ pub fn from_ref(content: &Content) -> Option<&Self> {
+ if content.is::<T>() {
+ // Safety:
+ // - We have checked the type.
+ // - Packed<T> is repr(transparent).
+ return Some(unsafe { std::mem::transmute::<&Content, &Packed<T>>(content) });
+ }
+ None
+ }
+
+ /// Try to cast type-erased content into a statically known packed element.
+ pub fn from_mut(content: &mut Content) -> Option<&mut Self> {
+ if content.is::<T>() {
+ // Safety:
+ // - We have checked the type.
+ // - Packed<T> is repr(transparent).
+ return Some(unsafe {
+ std::mem::transmute::<&mut Content, &mut Packed<T>>(content)
+ });
+ }
+ None
+ }
+
+ /// Try to cast type-erased content into a statically known packed element.
+ pub fn from_owned(content: Content) -> Result<Self, Content> {
+ if content.is::<T>() {
+ // Safety:
+ // - We have checked the type.
+ // - Packed<T> is repr(transparent).
+ return Ok(unsafe { std::mem::transmute::<Content, Packed<T>>(content) });
+ }
+ Err(content)
+ }
+
+ /// Pack back into content.
+ pub fn pack(self) -> Content {
+ self.0
+ }
+
+ /// Extract the raw underlying element.
+ pub fn unpack(self) -> T {
+ // This function doesn't yet need owned self, but might in the future.
+ (*self).clone()
+ }
+
+ /// The element's span.
+ pub fn span(&self) -> Span {
+ self.0.span()
+ }
+
+ /// Set the span of the element.
+ pub fn spanned(self, span: Span) -> Self {
+ Self(self.0.spanned(span), PhantomData)
+ }
+
+ /// Accesses the label of the element.
+ pub fn label(&self) -> Option<Label> {
+ self.0.label()
+ }
+
+ /// Accesses the location of the element.
+ pub fn location(&self) -> Option<Location> {
+ self.0.location()
+ }
+
+ /// Sets the location of the element.
+ pub fn set_location(&mut self, location: Location) {
+ self.0.set_location(location);
+ }
+}
+
+impl<T: NativeElement> AsRef<T> for Packed<T> {
+ fn as_ref(&self) -> &T {
+ self
+ }
+}
+
+impl<T: NativeElement> AsMut<T> for Packed<T> {
+ fn as_mut(&mut self) -> &mut T {
+ self
+ }
+}
+
+impl<T: NativeElement> Deref for Packed<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ // Safety:
+ // - Packed<T> guarantees that the content trait object wraps
+ // an element of type `T`.
+ // - This downcast works the same way as dyn Any's does. We can't reuse
+ // that one because we don't want to pay the cost for every deref.
+ let elem = &*self.0.inner.elem;
+ unsafe { &*(elem as *const dyn Bounds as *const T) }
+ }
+}
+
+impl<T: NativeElement> DerefMut for Packed<T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ // Safety:
+ // - Packed<T> guarantees that the content trait object wraps
+ // an element of type `T`.
+ // - We have guaranteed unique access thanks to `make_mut`.
+ // - This downcast works the same way as dyn Any's does. We can't reuse
+ // that one because we don't want to pay the cost for every deref.
+ let elem = &mut *self.0.make_mut().elem;
+ unsafe { &mut *(elem as *mut dyn Bounds as *mut T) }
+ }
+}
+
+impl<T: NativeElement + Debug> Debug for Packed<T> {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+/// A sequence of content.
+#[elem(Debug, Repr, PartialEq)]
+pub struct SequenceElem {
+ /// The elements.
+ #[required]
+ pub children: Vec<Content>,
+}
+
+impl Debug for SequenceElem {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "Sequence ")?;
+ f.debug_list().entries(&self.children).finish()
+ }
+}
+
+// Derive is currently incompatible with `elem` macro.
+#[allow(clippy::derivable_impls)]
+impl Default for SequenceElem {
+ fn default() -> Self {
+ Self { children: Default::default() }
+ }
+}
+
+impl PartialEq for SequenceElem {
+ fn eq(&self, other: &Self) -> bool {
+ self.children.iter().eq(other.children.iter())
+ }
+}
+
+impl Repr for SequenceElem {
+ fn repr(&self) -> EcoString {
+ if self.children.is_empty() {
+ "[]".into()
+ } else {
+ let elements = crate::foundations::repr::pretty_array_like(
+ &self.children.iter().map(|c| c.inner.elem.repr()).collect::<Vec<_>>(),
+ false,
+ );
+ eco_format!("sequence{}", elements)
+ }
+ }
+}
+
+/// Content alongside styles.
+#[elem(Debug, Repr, PartialEq)]
+pub struct StyledElem {
+ /// The content.
+ #[required]
+ pub child: Content,
+ /// The styles.
+ #[required]
+ pub styles: Styles,
+}
+
+impl Debug for StyledElem {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ for style in self.styles.iter() {
+ writeln!(f, "#{style:?}")?;
+ }
+ self.child.fmt(f)
+ }
+}
+
+impl PartialEq for StyledElem {
+ fn eq(&self, other: &Self) -> bool {
+ self.child == other.child
+ }
+}
+
+impl Repr for StyledElem {
+ fn repr(&self) -> EcoString {
+ eco_format!("styled(child: {}, ..)", self.child.repr())
+ }
+}
+
+/// Tries to extract the plain-text representation of the element.
+pub trait PlainText {
+ /// Write this element's plain text into the given buffer.
+ fn plain_text(&self, text: &mut EcoString);
+}
+
+/// An error arising when trying to access a field of content.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum FieldAccessError {
+ Unknown,
+ Unset,
+ Internal,
+}
+
+impl FieldAccessError {
+ /// Formats the error message given the content and the field name.
+ #[cold]
+ pub fn message(self, content: &Content, field: &str) -> EcoString {
+ let elem_name = content.elem().name();
+ match self {
+ FieldAccessError::Unknown => {
+ eco_format!("{elem_name} does not have field {}", field.repr())
+ }
+ FieldAccessError::Unset => {
+ eco_format!(
+ "field {} in {elem_name} is not known at this point",
+ field.repr()
+ )
+ }
+ FieldAccessError::Internal => {
+ eco_format!(
+ "internal error when accessing field {} in {elem_name} – this is a bug",
+ field.repr()
+ )
+ }
+ }
+ }
+
+ /// Formats the error message for an `at` calls without a default value.
+ #[cold]
+ pub fn message_no_default(self, content: &Content, field: &str) -> EcoString {
+ let mut msg = self.message(content, field);
+ msg.push_str(" and no default was specified");
+ msg
+ }
+}