mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
979 lines
28 KiB
Rust
979 lines
28 KiB
Rust
use std::any::{Any, TypeId};
|
|
use std::fmt::{self, Debug, Formatter};
|
|
use std::hash::{Hash, Hasher};
|
|
use std::{mem, ptr};
|
|
|
|
use comemo::Tracked;
|
|
use ecow::{eco_vec, EcoString, EcoVec};
|
|
use smallvec::SmallVec;
|
|
use typst_syntax::Span;
|
|
use typst_utils::LazyHash;
|
|
|
|
use crate::diag::{SourceResult, Trace, Tracepoint};
|
|
use crate::engine::Engine;
|
|
use crate::foundations::{
|
|
cast, ty, Content, Context, Element, Func, NativeElement, Repr, Selector,
|
|
};
|
|
use crate::text::{FontFamily, FontList, TextElem};
|
|
|
|
/// A list of style properties.
|
|
#[ty(cast)]
|
|
#[derive(Default, PartialEq, Clone, Hash)]
|
|
pub struct Styles(EcoVec<LazyHash<Style>>);
|
|
|
|
impl Styles {
|
|
/// Create a new, empty style list.
|
|
pub const fn new() -> Self {
|
|
Self(EcoVec::new())
|
|
}
|
|
|
|
/// Whether this contains no styles.
|
|
pub fn is_empty(&self) -> bool {
|
|
self.0.is_empty()
|
|
}
|
|
|
|
/// Iterate over the contained styles.
|
|
pub fn iter(&self) -> impl Iterator<Item = &Style> {
|
|
self.0.iter().map(|style| &**style)
|
|
}
|
|
|
|
/// Iterate over the contained styles.
|
|
pub fn as_slice(&self) -> &[LazyHash<Style>] {
|
|
self.0.as_slice()
|
|
}
|
|
|
|
/// Set an inner value for a style property.
|
|
///
|
|
/// If the property needs folding and the value is already contained in the
|
|
/// style map, `self` contributes the outer values and `value` is the inner
|
|
/// one.
|
|
pub fn set(&mut self, style: impl Into<Style>) {
|
|
self.0.push(LazyHash::new(style.into()));
|
|
}
|
|
|
|
/// Remove the style that was last set.
|
|
pub fn unset(&mut self) {
|
|
self.0.pop();
|
|
}
|
|
|
|
/// Apply outer styles. Like [`chain`](StyleChain::chain), but in-place.
|
|
pub fn apply(&mut self, mut outer: Self) {
|
|
outer.0.extend(mem::take(self).0);
|
|
*self = outer;
|
|
}
|
|
|
|
/// Apply one outer styles.
|
|
pub fn apply_one(&mut self, outer: Style) {
|
|
self.0.insert(0, LazyHash::new(outer));
|
|
}
|
|
|
|
/// Add an origin span to all contained properties.
|
|
pub fn spanned(mut self, span: Span) -> Self {
|
|
for entry in self.0.make_mut() {
|
|
if let Style::Property(property) = &mut **entry {
|
|
property.span = span;
|
|
}
|
|
}
|
|
self
|
|
}
|
|
|
|
/// Marks the styles as having been applied outside of any show rule.
|
|
pub fn outside(mut self) -> Self {
|
|
for entry in self.0.make_mut() {
|
|
match &mut **entry {
|
|
Style::Property(property) => property.outside = true,
|
|
Style::Recipe(recipe) => recipe.outside = true,
|
|
_ => {}
|
|
}
|
|
}
|
|
self
|
|
}
|
|
|
|
/// Marks the styles as being allowed to be lifted up to the page level.
|
|
pub fn liftable(mut self) -> Self {
|
|
for entry in self.0.make_mut() {
|
|
if let Style::Property(property) = &mut **entry {
|
|
property.liftable = true;
|
|
}
|
|
}
|
|
self
|
|
}
|
|
|
|
/// Whether there is a style for the given field of the given element.
|
|
pub fn has<T: NativeElement>(&self, field: u8) -> bool {
|
|
let elem = T::elem();
|
|
self.0
|
|
.iter()
|
|
.filter_map(|style| style.property())
|
|
.any(|property| property.is_of(elem) && property.id == field)
|
|
}
|
|
|
|
/// Set a font family composed of a preferred family and existing families
|
|
/// from a style chain.
|
|
pub fn set_family(&mut self, preferred: FontFamily, existing: StyleChain) {
|
|
self.set(TextElem::set_font(FontList(
|
|
std::iter::once(preferred)
|
|
.chain(TextElem::font_in(existing).into_iter().cloned())
|
|
.collect(),
|
|
)));
|
|
}
|
|
}
|
|
|
|
impl From<LazyHash<Style>> for Styles {
|
|
fn from(style: LazyHash<Style>) -> Self {
|
|
Self(eco_vec![style])
|
|
}
|
|
}
|
|
|
|
impl From<Style> for Styles {
|
|
fn from(style: Style) -> Self {
|
|
Self(eco_vec![LazyHash::new(style)])
|
|
}
|
|
}
|
|
|
|
impl IntoIterator for Styles {
|
|
type Item = LazyHash<Style>;
|
|
type IntoIter = ecow::vec::IntoIter<Self::Item>;
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
self.0.into_iter()
|
|
}
|
|
}
|
|
|
|
impl FromIterator<LazyHash<Style>> for Styles {
|
|
fn from_iter<T: IntoIterator<Item = LazyHash<Style>>>(iter: T) -> Self {
|
|
Self(iter.into_iter().collect())
|
|
}
|
|
}
|
|
|
|
impl Debug for Styles {
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
f.write_str("Styles ")?;
|
|
f.debug_list().entries(&self.0).finish()
|
|
}
|
|
}
|
|
|
|
impl Repr for Styles {
|
|
fn repr(&self) -> EcoString {
|
|
"..".into()
|
|
}
|
|
}
|
|
|
|
/// A single style property or recipe.
|
|
#[derive(Clone, Hash)]
|
|
pub enum Style {
|
|
/// A style property originating from a set rule or constructor.
|
|
Property(Property),
|
|
/// A show rule recipe.
|
|
Recipe(Recipe),
|
|
/// Disables a specific show rule recipe.
|
|
///
|
|
/// Note: This currently only works for regex recipes since it's the only
|
|
/// place we need it for the moment. Normal show rules use guards directly
|
|
/// on elements instead.
|
|
Revocation(RecipeIndex),
|
|
}
|
|
|
|
impl Style {
|
|
/// If this is a property, return it.
|
|
pub fn property(&self) -> Option<&Property> {
|
|
match self {
|
|
Self::Property(property) => Some(property),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// If this is a recipe, return it.
|
|
pub fn recipe(&self) -> Option<&Recipe> {
|
|
match self {
|
|
Self::Recipe(recipe) => Some(recipe),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// The style's span, if any.
|
|
pub fn span(&self) -> Span {
|
|
match self {
|
|
Self::Property(property) => property.span,
|
|
Self::Recipe(recipe) => recipe.span,
|
|
Self::Revocation(_) => Span::detached(),
|
|
}
|
|
}
|
|
|
|
/// Returns `Some(_)` with an optional span if this style is for
|
|
/// the given element.
|
|
pub fn element(&self) -> Option<Element> {
|
|
match self {
|
|
Style::Property(property) => Some(property.elem),
|
|
Style::Recipe(recipe) => match recipe.selector {
|
|
Some(Selector::Elem(elem, _)) => Some(elem),
|
|
_ => None,
|
|
},
|
|
Style::Revocation(_) => None,
|
|
}
|
|
}
|
|
|
|
/// Whether the style is allowed to be lifted up to the page level. Only
|
|
/// true for styles originating from set rules.
|
|
pub fn liftable(&self) -> bool {
|
|
match self {
|
|
Self::Property(property) => property.liftable,
|
|
Self::Recipe(_) => true,
|
|
Self::Revocation(_) => false,
|
|
}
|
|
}
|
|
|
|
/// Whether the style was applied outside of any show rule. This is set
|
|
/// during realization.
|
|
pub fn outside(&self) -> bool {
|
|
match self {
|
|
Self::Property(property) => property.outside,
|
|
Self::Recipe(recipe) => recipe.outside,
|
|
Self::Revocation(_) => false,
|
|
}
|
|
}
|
|
|
|
/// Turn this style into prehashed style.
|
|
pub fn wrap(self) -> LazyHash<Style> {
|
|
LazyHash::new(self)
|
|
}
|
|
}
|
|
|
|
impl Debug for Style {
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
match self {
|
|
Self::Property(property) => property.fmt(f),
|
|
Self::Recipe(recipe) => recipe.fmt(f),
|
|
Self::Revocation(guard) => guard.fmt(f),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Property> for Style {
|
|
fn from(property: Property) -> Self {
|
|
Self::Property(property)
|
|
}
|
|
}
|
|
|
|
impl From<Recipe> for Style {
|
|
fn from(recipe: Recipe) -> Self {
|
|
Self::Recipe(recipe)
|
|
}
|
|
}
|
|
|
|
/// A style property originating from a set rule or constructor.
|
|
#[derive(Clone, Hash)]
|
|
pub struct Property {
|
|
/// The element the property belongs to.
|
|
elem: Element,
|
|
/// The property's ID.
|
|
id: u8,
|
|
/// The property's value.
|
|
value: Block,
|
|
/// The span of the set rule the property stems from.
|
|
span: Span,
|
|
/// Whether the property is allowed to be lifted up to the page level.
|
|
liftable: bool,
|
|
/// Whether the property was applied outside of any show rule.
|
|
outside: bool,
|
|
}
|
|
|
|
impl Property {
|
|
/// Create a new property from a key-value pair.
|
|
pub fn new<E, T>(id: u8, value: T) -> Self
|
|
where
|
|
E: NativeElement,
|
|
T: Debug + Clone + Hash + Send + Sync + 'static,
|
|
{
|
|
Self {
|
|
elem: E::elem(),
|
|
id,
|
|
value: Block::new(value),
|
|
span: Span::detached(),
|
|
liftable: false,
|
|
outside: false,
|
|
}
|
|
}
|
|
|
|
/// Whether this property is the given one.
|
|
pub fn is(&self, elem: Element, id: u8) -> bool {
|
|
self.elem == elem && self.id == id
|
|
}
|
|
|
|
/// Whether this property belongs to the given element.
|
|
pub fn is_of(&self, elem: Element) -> bool {
|
|
self.elem == elem
|
|
}
|
|
|
|
/// Turn this property into prehashed style.
|
|
pub fn wrap(self) -> LazyHash<Style> {
|
|
Style::Property(self).wrap()
|
|
}
|
|
}
|
|
|
|
impl Debug for Property {
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
write!(
|
|
f,
|
|
"Set({}.{}: ",
|
|
self.elem.name(),
|
|
self.elem.field_name(self.id).unwrap()
|
|
)?;
|
|
self.value.fmt(f)?;
|
|
write!(f, ")")
|
|
}
|
|
}
|
|
|
|
/// A block storage for storing style values.
|
|
///
|
|
/// We're using a `Box` since values will either be contained in an `Arc` and
|
|
/// therefore already on the heap or they will be small enough that we can just
|
|
/// clone them.
|
|
#[derive(Hash)]
|
|
struct Block(Box<dyn Blockable>);
|
|
|
|
impl Block {
|
|
/// Creates a new block.
|
|
fn new<T: Blockable>(value: T) -> Self {
|
|
Self(Box::new(value))
|
|
}
|
|
|
|
/// Downcasts the block to the specified type.
|
|
fn downcast<T: 'static>(&self) -> Option<&T> {
|
|
self.0.as_any().downcast_ref()
|
|
}
|
|
}
|
|
|
|
impl Debug for Block {
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
self.0.fmt(f)
|
|
}
|
|
}
|
|
|
|
impl Clone for Block {
|
|
fn clone(&self) -> Self {
|
|
self.0.dyn_clone()
|
|
}
|
|
}
|
|
|
|
/// A value that can be stored in a block.
|
|
///
|
|
/// Auto derived for all types that implement [`Any`], [`Clone`], [`Hash`],
|
|
/// [`Debug`], [`Send`] and [`Sync`].
|
|
trait Blockable: Debug + Send + Sync + 'static {
|
|
/// Equivalent to `downcast_ref` for the block.
|
|
fn as_any(&self) -> &dyn Any;
|
|
|
|
/// Equivalent to [`Hash`] for the block.
|
|
fn dyn_hash(&self, state: &mut dyn Hasher);
|
|
|
|
/// Equivalent to [`Clone`] for the block.
|
|
fn dyn_clone(&self) -> Block;
|
|
}
|
|
|
|
impl<T: Debug + Clone + Hash + Send + Sync + 'static> Blockable for T {
|
|
fn as_any(&self) -> &dyn Any {
|
|
self
|
|
}
|
|
|
|
fn dyn_hash(&self, mut state: &mut dyn Hasher) {
|
|
// Also hash the TypeId since values with different types but
|
|
// equal data should be different.
|
|
TypeId::of::<Self>().hash(&mut state);
|
|
self.hash(&mut state);
|
|
}
|
|
|
|
fn dyn_clone(&self) -> Block {
|
|
Block(Box::new(self.clone()))
|
|
}
|
|
}
|
|
|
|
impl Hash for dyn Blockable {
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
self.dyn_hash(state);
|
|
}
|
|
}
|
|
|
|
/// A show rule recipe.
|
|
#[derive(Clone, PartialEq, Hash)]
|
|
pub struct Recipe {
|
|
/// Determines whether the recipe applies to an element.
|
|
///
|
|
/// If this is `None`, then this recipe is from a show rule with
|
|
/// no selector (`show: rest => ...`), which is [eagerly applied][Content::styled_with_recipe]
|
|
/// to the rest of the content in the scope.
|
|
selector: Option<Selector>,
|
|
/// The transformation to perform on the match.
|
|
transform: Transformation,
|
|
/// The span that errors are reported with.
|
|
span: Span,
|
|
/// Relevant properties of the kind of construct the style originated from
|
|
/// and where it was applied.
|
|
outside: bool,
|
|
}
|
|
|
|
impl Recipe {
|
|
/// Create a new recipe from a key-value pair.
|
|
pub fn new(
|
|
selector: Option<Selector>,
|
|
transform: Transformation,
|
|
span: Span,
|
|
) -> Self {
|
|
Self { selector, transform, span, outside: false }
|
|
}
|
|
|
|
/// The recipe's selector.
|
|
pub fn selector(&self) -> Option<&Selector> {
|
|
self.selector.as_ref()
|
|
}
|
|
|
|
/// The recipe's transformation.
|
|
pub fn transform(&self) -> &Transformation {
|
|
&self.transform
|
|
}
|
|
|
|
/// The recipe's span.
|
|
pub fn span(&self) -> Span {
|
|
self.span
|
|
}
|
|
|
|
/// Apply the recipe to the given content.
|
|
pub fn apply(
|
|
&self,
|
|
engine: &mut Engine,
|
|
context: Tracked<Context>,
|
|
content: Content,
|
|
) -> SourceResult<Content> {
|
|
let mut content = match &self.transform {
|
|
Transformation::Content(content) => content.clone(),
|
|
Transformation::Func(func) => {
|
|
let mut result = func.call(engine, context, [content.clone()]);
|
|
if self.selector.is_some() {
|
|
let point = || Tracepoint::Show(content.func().name().into());
|
|
result = result.trace(engine.world, point, content.span());
|
|
}
|
|
result?.display()
|
|
}
|
|
Transformation::Style(styles) => content.styled_with_map(styles.clone()),
|
|
};
|
|
if content.span().is_detached() {
|
|
content = content.spanned(self.span);
|
|
}
|
|
Ok(content)
|
|
}
|
|
}
|
|
|
|
impl Debug for Recipe {
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
f.write_str("Show(")?;
|
|
if let Some(selector) = &self.selector {
|
|
selector.fmt(f)?;
|
|
f.write_str(", ")?;
|
|
}
|
|
self.transform.fmt(f)
|
|
}
|
|
}
|
|
|
|
/// Identifies a show rule recipe from the top of the chain.
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
|
pub struct RecipeIndex(pub usize);
|
|
|
|
/// A show rule transformation that can be applied to a match.
|
|
#[derive(Clone, PartialEq, Hash)]
|
|
pub enum Transformation {
|
|
/// Replacement content.
|
|
Content(Content),
|
|
/// A function to apply to the match.
|
|
Func(Func),
|
|
/// Apply styles to the content.
|
|
Style(Styles),
|
|
}
|
|
|
|
impl Debug for Transformation {
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
match self {
|
|
Self::Content(content) => content.fmt(f),
|
|
Self::Func(func) => func.fmt(f),
|
|
Self::Style(styles) => styles.fmt(f),
|
|
}
|
|
}
|
|
}
|
|
|
|
cast! {
|
|
Transformation,
|
|
content: Content => Self::Content(content),
|
|
func: Func => Self::Func(func),
|
|
}
|
|
|
|
/// A chain of styles, similar to a linked list.
|
|
///
|
|
/// A style chain allows to combine properties from multiple style lists in a
|
|
/// element hierarchy in a non-allocating way. Rather than eagerly merging the
|
|
/// lists, each access walks the hierarchy from the innermost to the outermost
|
|
/// map, trying to find a match and then folding it with matches further up the
|
|
/// chain.
|
|
#[derive(Default, Clone, Copy, Hash)]
|
|
pub struct StyleChain<'a> {
|
|
/// The first link of this chain.
|
|
head: &'a [LazyHash<Style>],
|
|
/// The remaining links in the chain.
|
|
tail: Option<&'a Self>,
|
|
}
|
|
|
|
impl<'a> StyleChain<'a> {
|
|
/// Start a new style chain with root styles.
|
|
pub fn new(root: &'a Styles) -> Self {
|
|
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)
|
|
}
|
|
|
|
/// Cast the first value for the given property in the chain.
|
|
pub fn get<T: Clone + 'static>(
|
|
self,
|
|
func: Element,
|
|
id: u8,
|
|
inherent: Option<&T>,
|
|
default: impl Fn() -> T,
|
|
) -> T {
|
|
self.properties::<T>(func, id, inherent)
|
|
.next()
|
|
.cloned()
|
|
.unwrap_or_else(default)
|
|
}
|
|
|
|
/// Cast the first value for the given property in the chain,
|
|
/// returning a borrowed value.
|
|
pub fn get_ref<T: 'static>(
|
|
self,
|
|
func: Element,
|
|
id: u8,
|
|
inherent: Option<&'a T>,
|
|
default: impl Fn() -> &'a T,
|
|
) -> &'a T {
|
|
self.properties::<T>(func, id, inherent)
|
|
.next()
|
|
.unwrap_or_else(default)
|
|
}
|
|
|
|
/// Cast the first value for the given property in the chain, taking
|
|
/// `Fold` implementations into account.
|
|
pub fn get_folded<T: Fold + Clone + 'static>(
|
|
self,
|
|
func: Element,
|
|
id: u8,
|
|
inherent: Option<&T>,
|
|
default: impl Fn() -> T,
|
|
) -> T {
|
|
fn next<T: Fold>(
|
|
mut values: impl Iterator<Item = T>,
|
|
default: &impl Fn() -> T,
|
|
) -> T {
|
|
values
|
|
.next()
|
|
.map(|value| value.fold(next(values, default)))
|
|
.unwrap_or_else(default)
|
|
}
|
|
next(self.properties::<T>(func, id, inherent).cloned(), &default)
|
|
}
|
|
|
|
/// Iterate over all values for the given property in the chain.
|
|
fn properties<T: 'static>(
|
|
self,
|
|
func: Element,
|
|
id: u8,
|
|
inherent: Option<&'a T>,
|
|
) -> impl Iterator<Item = &'a T> {
|
|
inherent.into_iter().chain(
|
|
self.entries()
|
|
.filter_map(|style| style.property())
|
|
.filter(move |property| property.is(func, id))
|
|
.map(|property| &property.value)
|
|
.map(move |value| {
|
|
value.downcast().unwrap_or_else(|| {
|
|
panic!(
|
|
"attempted to read a value of a different type than was written {}.{}: {:?}",
|
|
func.name(),
|
|
func.field_name(id).unwrap(),
|
|
value
|
|
)
|
|
})
|
|
}),
|
|
)
|
|
}
|
|
|
|
/// Iterate over the entries of the chain.
|
|
pub fn entries(self) -> Entries<'a> {
|
|
Entries { inner: [].as_slice().iter(), links: self.links() }
|
|
}
|
|
|
|
/// Iterate over the recipes in the chain.
|
|
pub fn recipes(self) -> impl Iterator<Item = &'a Recipe> {
|
|
self.entries().filter_map(|style| style.recipe())
|
|
}
|
|
|
|
/// Iterate over the links of the chain.
|
|
pub fn links(self) -> Links<'a> {
|
|
Links(Some(self))
|
|
}
|
|
|
|
/// Convert to a style map.
|
|
pub fn to_map(self) -> Styles {
|
|
let mut styles: EcoVec<_> = self.entries().cloned().collect();
|
|
styles.make_mut().reverse();
|
|
Styles(styles)
|
|
}
|
|
|
|
/// Build owned styles from the suffix (all links beyond the `len`) of the
|
|
/// chain.
|
|
pub fn suffix(self, len: usize) -> Styles {
|
|
let mut styles = EcoVec::new();
|
|
let take = self.links().count().saturating_sub(len);
|
|
for link in self.links().take(take) {
|
|
styles.extend(link.iter().cloned().rev());
|
|
}
|
|
styles.make_mut().reverse();
|
|
Styles(styles)
|
|
}
|
|
|
|
/// Remove the last link from the chain.
|
|
pub fn pop(&mut self) {
|
|
*self = self.tail.copied().unwrap_or_default();
|
|
}
|
|
|
|
/// Determine the shared trunk of a collection of style chains.
|
|
pub fn trunk(iter: impl IntoIterator<Item = Self>) -> Option<Self> {
|
|
// Determine shared style depth and first span.
|
|
let mut iter = iter.into_iter();
|
|
let mut trunk = iter.next()?;
|
|
let mut depth = trunk.links().count();
|
|
|
|
for mut chain in iter {
|
|
let len = chain.links().count();
|
|
if len < depth {
|
|
for _ in 0..depth - len {
|
|
trunk.pop();
|
|
}
|
|
depth = len;
|
|
} else if len > depth {
|
|
for _ in 0..len - depth {
|
|
chain.pop();
|
|
}
|
|
}
|
|
|
|
while depth > 0 && chain != trunk {
|
|
trunk.pop();
|
|
chain.pop();
|
|
depth -= 1;
|
|
}
|
|
}
|
|
|
|
Some(trunk)
|
|
}
|
|
}
|
|
|
|
impl Debug for StyleChain<'_> {
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
f.write_str("StyleChain ")?;
|
|
f.debug_list()
|
|
.entries(self.entries().collect::<Vec<_>>().into_iter().rev())
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl PartialEq for StyleChain<'_> {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
ptr::eq(self.head, other.head)
|
|
&& match (self.tail, other.tail) {
|
|
(Some(a), Some(b)) => ptr::eq(a, b),
|
|
(None, None) => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Things that can be attached to a style chain.
|
|
pub trait Chainable {
|
|
/// Attach `self` as the first link of the chain.
|
|
fn chain<'a>(&'a self, outer: &'a StyleChain<'_>) -> StyleChain<'a>;
|
|
}
|
|
|
|
impl Chainable for LazyHash<Style> {
|
|
fn chain<'a>(&'a self, outer: &'a StyleChain<'_>) -> StyleChain<'a> {
|
|
StyleChain {
|
|
head: std::slice::from_ref(self),
|
|
tail: Some(outer),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Chainable for [LazyHash<Style>] {
|
|
fn chain<'a>(&'a self, outer: &'a StyleChain<'_>) -> StyleChain<'a> {
|
|
if self.is_empty() {
|
|
*outer
|
|
} else {
|
|
StyleChain { head: self, tail: Some(outer) }
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<const N: usize> Chainable for [LazyHash<Style>; N] {
|
|
fn chain<'a>(&'a self, outer: &'a StyleChain<'_>) -> StyleChain<'a> {
|
|
Chainable::chain(self.as_slice(), outer)
|
|
}
|
|
}
|
|
|
|
impl Chainable for Styles {
|
|
fn chain<'a>(&'a self, outer: &'a StyleChain<'_>) -> StyleChain<'a> {
|
|
Chainable::chain(self.0.as_slice(), outer)
|
|
}
|
|
}
|
|
|
|
/// An iterator over the entries in a style chain.
|
|
pub struct Entries<'a> {
|
|
inner: std::slice::Iter<'a, LazyHash<Style>>,
|
|
links: Links<'a>,
|
|
}
|
|
|
|
impl<'a> Iterator for Entries<'a> {
|
|
type Item = &'a LazyHash<Style>;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
loop {
|
|
if let Some(entry) = self.inner.next_back() {
|
|
return Some(entry);
|
|
}
|
|
|
|
match self.links.next() {
|
|
Some(next) => self.inner = next.iter(),
|
|
None => return None,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An iterator over the links of a style chain.
|
|
pub struct Links<'a>(Option<StyleChain<'a>>);
|
|
|
|
impl<'a> Iterator for Links<'a> {
|
|
type Item = &'a [LazyHash<Style>];
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
let StyleChain { head, tail } = self.0?;
|
|
self.0 = tail.copied();
|
|
Some(head)
|
|
}
|
|
}
|
|
|
|
/// A sequence of elements with associated styles.
|
|
#[derive(Clone, PartialEq, Hash)]
|
|
pub struct StyleVec {
|
|
/// The elements themselves.
|
|
elements: EcoVec<Content>,
|
|
/// A run-length encoded list of style lists.
|
|
///
|
|
/// Each element is a (styles, count) pair. Any elements whose
|
|
/// style falls after the end of this list is considered to
|
|
/// have an empty style list.
|
|
styles: EcoVec<(Styles, usize)>,
|
|
}
|
|
|
|
impl StyleVec {
|
|
/// Create a style vector from an unstyled vector content.
|
|
pub fn wrap(elements: EcoVec<Content>) -> Self {
|
|
Self { elements, styles: EcoVec::new() }
|
|
}
|
|
|
|
/// Create a `StyleVec` from a list of content with style chains.
|
|
pub fn create<'a>(buf: &[(&'a Content, StyleChain<'a>)]) -> (Self, StyleChain<'a>) {
|
|
let trunk = StyleChain::trunk(buf.iter().map(|&(_, s)| s)).unwrap_or_default();
|
|
let depth = trunk.links().count();
|
|
|
|
let mut elements = EcoVec::with_capacity(buf.len());
|
|
let mut styles = EcoVec::<(Styles, usize)>::new();
|
|
let mut last: Option<(StyleChain<'a>, usize)> = None;
|
|
|
|
for &(element, chain) in buf {
|
|
elements.push(element.clone());
|
|
|
|
if let Some((prev, run)) = &mut last {
|
|
if chain == *prev {
|
|
*run += 1;
|
|
} else {
|
|
styles.push((prev.suffix(depth), *run));
|
|
last = Some((chain, 1));
|
|
}
|
|
} else {
|
|
last = Some((chain, 1));
|
|
}
|
|
}
|
|
|
|
if let Some((last, run)) = last {
|
|
let skippable = styles.is_empty() && last == trunk;
|
|
if !skippable {
|
|
styles.push((last.suffix(depth), run));
|
|
}
|
|
}
|
|
|
|
(StyleVec { elements, styles }, trunk)
|
|
}
|
|
|
|
/// Whether there are no elements.
|
|
pub fn is_empty(&self) -> bool {
|
|
self.elements.is_empty()
|
|
}
|
|
|
|
/// The number of elements.
|
|
pub fn len(&self) -> usize {
|
|
self.elements.len()
|
|
}
|
|
|
|
/// Iterate over the contained content and style chains.
|
|
pub fn iter<'a>(
|
|
&'a self,
|
|
outer: &'a StyleChain<'_>,
|
|
) -> impl Iterator<Item = (&'a Content, StyleChain<'a>)> {
|
|
static EMPTY: Styles = Styles::new();
|
|
self.elements
|
|
.iter()
|
|
.zip(
|
|
self.styles
|
|
.iter()
|
|
.flat_map(|(local, count)| std::iter::repeat(local).take(*count))
|
|
.chain(std::iter::repeat(&EMPTY)),
|
|
)
|
|
.map(|(element, local)| (element, outer.chain(local)))
|
|
}
|
|
|
|
/// Get a style property, but only if it is the same for all children of the
|
|
/// style vector.
|
|
pub fn shared_get<T: PartialEq>(
|
|
&self,
|
|
styles: StyleChain<'_>,
|
|
getter: fn(StyleChain) -> T,
|
|
) -> Option<T> {
|
|
let value = getter(styles);
|
|
self.styles
|
|
.iter()
|
|
.all(|(local, _)| getter(styles.chain(local)) == value)
|
|
.then_some(value)
|
|
}
|
|
}
|
|
|
|
impl Debug for StyleVec {
|
|
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
|
f.debug_list().entries(&self.elements).finish()
|
|
}
|
|
}
|
|
|
|
/// A property that is resolved with other properties from the style chain.
|
|
pub trait Resolve {
|
|
/// The type of the resolved output.
|
|
type Output;
|
|
|
|
/// Resolve the value using the style chain.
|
|
fn resolve(self, styles: StyleChain) -> Self::Output;
|
|
}
|
|
|
|
impl<T: Resolve> Resolve for Option<T> {
|
|
type Output = Option<T::Output>;
|
|
|
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
|
self.map(|v| v.resolve(styles))
|
|
}
|
|
}
|
|
|
|
/// A property that is folded to determine its final value.
|
|
///
|
|
/// In the example below, the chain of stroke values is folded into a single
|
|
/// value: `4pt + red`.
|
|
///
|
|
/// ```example
|
|
/// #set rect(stroke: red)
|
|
/// #set rect(stroke: 4pt)
|
|
/// #rect()
|
|
/// ```
|
|
pub trait Fold {
|
|
/// Fold this inner value with an outer folded value.
|
|
fn fold(self, outer: Self) -> Self;
|
|
}
|
|
|
|
impl Fold for bool {
|
|
fn fold(self, _: Self) -> Self {
|
|
self
|
|
}
|
|
}
|
|
|
|
impl<T: Fold> Fold for Option<T> {
|
|
fn fold(self, outer: Self) -> Self {
|
|
match (self, outer) {
|
|
(Some(inner), Some(outer)) => Some(inner.fold(outer)),
|
|
// An explicit `None` should be respected, thus we don't do
|
|
// `inner.or(outer)`.
|
|
(inner, _) => inner,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> Fold for Vec<T> {
|
|
fn fold(self, mut outer: Self) -> Self {
|
|
outer.extend(self);
|
|
outer
|
|
}
|
|
}
|
|
|
|
impl<T, const N: usize> Fold for SmallVec<[T; N]> {
|
|
fn fold(self, mut outer: Self) -> Self {
|
|
outer.extend(self);
|
|
outer
|
|
}
|
|
}
|
|
|
|
/// 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
|
|
/// `None`). Instead, when either of the `Option` objects is `None`, the other
|
|
/// one is necessarily returned by `fold_or`. Normal folding still occurs when
|
|
/// both values are `Some`, using `T`'s `Fold` implementation.
|
|
///
|
|
/// This is useful when `None` in a particular context means "unspecified"
|
|
/// rather than "absent", in which case a specified value (`Some`) is chosen
|
|
/// over an unspecified one (`None`), while two specified values are folded
|
|
/// together.
|
|
pub trait AlternativeFold {
|
|
/// Attempts to fold this inner value with an outer value. However, if
|
|
/// either value is `None`, returns the other one instead of folding.
|
|
fn fold_or(self, outer: Self) -> Self;
|
|
}
|
|
|
|
impl<T: Fold> AlternativeFold for Option<T> {
|
|
fn fold_or(self, outer: Self) -> Self {
|
|
match (self, outer) {
|
|
(Some(inner), Some(outer)) => Some(inner.fold(outer)),
|
|
// If one of values is `None`, return the other one instead of
|
|
// folding.
|
|
(inner, outer) => inner.or(outer),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A type that accumulates depth when folded.
|
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Hash)]
|
|
pub struct Depth(pub usize);
|
|
|
|
impl Fold for Depth {
|
|
fn fold(self, outer: Self) -> Self {
|
|
Self(outer.0 + self.0)
|
|
}
|
|
}
|