mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
654 lines
19 KiB
Rust
654 lines
19 KiB
Rust
use std::any::{Any, TypeId};
|
|
use std::fmt::{self, Debug, Formatter};
|
|
use std::hash::{Hash, Hasher};
|
|
use std::sync::Arc;
|
|
|
|
use super::{Args, Func, Span, Template, Value, Vm};
|
|
use crate::diag::{At, TypResult};
|
|
use crate::library::{PageNode, ParNode};
|
|
|
|
/// A map of style properties.
|
|
#[derive(Default, Clone, PartialEq, Hash)]
|
|
pub struct StyleMap {
|
|
/// Settable properties.
|
|
props: Vec<Entry>,
|
|
/// Show rule recipes.
|
|
recipes: Vec<Recipe>,
|
|
}
|
|
|
|
impl StyleMap {
|
|
/// Create a new, empty style map.
|
|
pub fn new() -> Self {
|
|
Self { props: vec![], recipes: vec![] }
|
|
}
|
|
|
|
/// Whether this map contains no styles.
|
|
pub fn is_empty(&self) -> bool {
|
|
self.props.is_empty() && self.recipes.is_empty()
|
|
}
|
|
|
|
/// Create a style map from a single property-value pair.
|
|
pub fn with<P: Property>(key: P, value: P::Value) -> Self {
|
|
let mut styles = Self::new();
|
|
styles.set(key, value);
|
|
styles
|
|
}
|
|
|
|
/// Set the value for a style property.
|
|
pub fn set<P: Property>(&mut self, key: P, value: P::Value) {
|
|
self.props.push(Entry::new(key, value));
|
|
}
|
|
|
|
/// Set a value for a style property if it is `Some(_)`.
|
|
pub fn set_opt<P: Property>(&mut self, key: P, value: Option<P::Value>) {
|
|
if let Some(value) = value {
|
|
self.set(key, value);
|
|
}
|
|
}
|
|
|
|
/// Set a recipe.
|
|
pub fn set_recipe(&mut self, node: TypeId, func: Func, span: Span) {
|
|
self.recipes.push(Recipe { node, func, span });
|
|
}
|
|
|
|
/// Mark all contained properties as _scoped_. This means that they only
|
|
/// apply to the first descendant node (of their type) in the hierarchy and
|
|
/// not its children, too. This is used by class constructors.
|
|
pub fn scoped(mut self) -> Self {
|
|
for entry in &mut self.props {
|
|
entry.scoped = true;
|
|
}
|
|
self
|
|
}
|
|
|
|
/// Whether this map contains scoped styles.
|
|
pub fn has_scoped(&self) -> bool {
|
|
self.props.iter().any(|e| e.scoped)
|
|
}
|
|
|
|
/// Make `self` the first link of the style chain `outer`.
|
|
///
|
|
/// The resulting style chain contains styles from `self` as well as
|
|
/// `outer`. The ones from `self` take precedence over the ones from
|
|
/// `outer`. For folded properties `self` contributes the inner value.
|
|
pub fn chain<'a>(&'a self, outer: &'a StyleChain<'a>) -> StyleChain<'a> {
|
|
if self.is_empty() {
|
|
*outer
|
|
} else {
|
|
StyleChain {
|
|
link: Some(Link::Map(self)),
|
|
outer: Some(outer),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Apply styles from `outer` in-place. The resulting style map is
|
|
/// equivalent to the style chain created by
|
|
/// `self.chain(StyleChain::new(outer))`.
|
|
///
|
|
/// This is useful over `chain` when you need an owned map without a
|
|
/// lifetime, for example, because you want to store the style map inside a
|
|
/// packed node.
|
|
pub fn apply(&mut self, outer: &Self) {
|
|
self.props.splice(0 .. 0, outer.props.iter().cloned());
|
|
self.recipes.splice(0 .. 0, outer.recipes.iter().cloned());
|
|
}
|
|
|
|
/// The highest-level interruption of the map.
|
|
pub fn interruption(&self) -> Option<Interruption> {
|
|
self.props.iter().filter_map(|entry| entry.interruption()).max()
|
|
}
|
|
}
|
|
|
|
impl Debug for StyleMap {
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
for entry in self.props.iter().rev() {
|
|
writeln!(f, "{:#?}", entry)?;
|
|
}
|
|
for recipe in self.recipes.iter().rev() {
|
|
writeln!(f, "#[Recipe for {:?} from {:?}]", recipe.node, recipe.span)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Determines whether a style could interrupt some composable structure.
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
|
pub enum Interruption {
|
|
/// The style forces a paragraph break.
|
|
Par,
|
|
/// The style forces a page break.
|
|
Page,
|
|
}
|
|
|
|
/// Style property keys.
|
|
///
|
|
/// This trait is not intended to be implemented manually, but rather through
|
|
/// the `#[class]` proc-macro.
|
|
pub trait Property: Sync + Send + 'static {
|
|
/// The type of value that is returned when getting this property from a
|
|
/// style map. For example, this could be [`Length`](crate::geom::Length)
|
|
/// for a `WIDTH` property.
|
|
type Value: Debug + Clone + PartialEq + Hash + Sync + Send + 'static;
|
|
|
|
/// The name of the property, used for debug printing.
|
|
const NAME: &'static str;
|
|
|
|
/// Whether the property needs folding.
|
|
const FOLDING: bool = false;
|
|
|
|
/// The type id of the node this property belongs to.
|
|
fn node_id() -> TypeId;
|
|
|
|
/// The default value of the property.
|
|
fn default() -> Self::Value;
|
|
|
|
/// A static reference to the default value of the property.
|
|
///
|
|
/// This is automatically implemented through lazy-initialization in the
|
|
/// `#[class]` macro. This way, expensive defaults don't need to be
|
|
/// recreated all the time.
|
|
fn default_ref() -> &'static Self::Value;
|
|
|
|
/// Fold the property with an outer value.
|
|
///
|
|
/// For example, this would fold a relative font size with an outer
|
|
/// absolute font size.
|
|
#[allow(unused_variables)]
|
|
fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value {
|
|
inner
|
|
}
|
|
}
|
|
|
|
/// Marker trait that indicates that a property doesn't need folding.
|
|
pub trait Nonfolding {}
|
|
|
|
/// An entry for a single style property.
|
|
#[derive(Clone)]
|
|
struct Entry {
|
|
pair: Arc<dyn Bounds>,
|
|
scoped: bool,
|
|
}
|
|
|
|
impl Entry {
|
|
fn new<P: Property>(key: P, value: P::Value) -> Self {
|
|
Self {
|
|
pair: Arc::new((key, value)),
|
|
scoped: false,
|
|
}
|
|
}
|
|
|
|
fn is<P: Property>(&self) -> bool {
|
|
self.pair.style_id() == TypeId::of::<P>()
|
|
}
|
|
|
|
fn is_of<T: 'static>(&self) -> bool {
|
|
self.pair.node_id() == TypeId::of::<T>()
|
|
}
|
|
|
|
fn is_of_id(&self, node: TypeId) -> bool {
|
|
self.pair.node_id() == node
|
|
}
|
|
|
|
fn downcast<P: Property>(&self) -> Option<&P::Value> {
|
|
self.pair.as_any().downcast_ref()
|
|
}
|
|
|
|
fn interruption(&self) -> Option<Interruption> {
|
|
if self.is_of::<PageNode>() {
|
|
Some(Interruption::Page)
|
|
} else if self.is_of::<ParNode>() {
|
|
Some(Interruption::Par)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Debug for Entry {
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
f.write_str("#[")?;
|
|
self.pair.dyn_fmt(f)?;
|
|
if self.scoped {
|
|
f.write_str(" (scoped)")?;
|
|
}
|
|
f.write_str("]")
|
|
}
|
|
}
|
|
|
|
impl PartialEq for Entry {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.pair.dyn_eq(other) && self.scoped == other.scoped
|
|
}
|
|
}
|
|
|
|
impl Hash for Entry {
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
state.write_u64(self.pair.hash64());
|
|
state.write_u8(self.scoped as u8);
|
|
}
|
|
}
|
|
|
|
/// This trait is implemented for pairs of zero-sized property keys and their
|
|
/// value types below. Although it is zero-sized, the property `P` must be part
|
|
/// of the implementing type so that we can use it in the methods (it must be a
|
|
/// constrained type parameter).
|
|
trait Bounds: Sync + Send + 'static {
|
|
fn as_any(&self) -> &dyn Any;
|
|
fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result;
|
|
fn dyn_eq(&self, other: &Entry) -> bool;
|
|
fn hash64(&self) -> u64;
|
|
fn node_id(&self) -> TypeId;
|
|
fn style_id(&self) -> TypeId;
|
|
}
|
|
|
|
impl<P: Property> Bounds for (P, P::Value) {
|
|
fn as_any(&self) -> &dyn Any {
|
|
&self.1
|
|
}
|
|
|
|
fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
write!(f, "{} = {:?}", P::NAME, self.1)
|
|
}
|
|
|
|
fn dyn_eq(&self, other: &Entry) -> bool {
|
|
self.style_id() == other.pair.style_id()
|
|
&& if let Some(other) = other.downcast::<P>() {
|
|
&self.1 == other
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
fn hash64(&self) -> u64 {
|
|
let mut state = fxhash::FxHasher64::default();
|
|
self.style_id().hash(&mut state);
|
|
self.1.hash(&mut state);
|
|
state.finish()
|
|
}
|
|
|
|
fn node_id(&self) -> TypeId {
|
|
P::node_id()
|
|
}
|
|
|
|
fn style_id(&self) -> TypeId {
|
|
TypeId::of::<P>()
|
|
}
|
|
}
|
|
|
|
/// A show rule recipe.
|
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
|
struct Recipe {
|
|
node: TypeId,
|
|
func: Func,
|
|
span: Span,
|
|
}
|
|
|
|
/// A chain of style maps, similar to a linked list.
|
|
///
|
|
/// A style chain allows to conceptually merge (and fold) properties from
|
|
/// multiple style maps in a node hierarchy in a non-allocating way. Rather than
|
|
/// eagerly merging the maps, 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.
|
|
link: Option<Link<'a>>,
|
|
/// The remaining links in the chain.
|
|
outer: Option<&'a Self>,
|
|
}
|
|
|
|
/// The two kinds of links in the chain.
|
|
#[derive(Clone, Copy, Hash)]
|
|
enum Link<'a> {
|
|
/// Just a map with styles.
|
|
Map(&'a StyleMap),
|
|
/// A barrier that, in combination with one more such barrier, stops scoped
|
|
/// styles for the node with this type id.
|
|
Barrier(TypeId),
|
|
}
|
|
|
|
impl<'a> StyleChain<'a> {
|
|
/// Start a new style chain with a root map.
|
|
pub fn new(map: &'a StyleMap) -> Self {
|
|
Self { link: Some(Link::Map(map)), outer: None }
|
|
}
|
|
|
|
/// The number of links in the chain.
|
|
pub fn len(self) -> usize {
|
|
self.links().count()
|
|
}
|
|
|
|
/// Build a style map from the suffix (all links beyond the `len`) of the
|
|
/// chain.
|
|
///
|
|
/// Panics if the suffix contains barrier links.
|
|
pub fn suffix(self, len: usize) -> StyleMap {
|
|
let mut suffix = StyleMap::new();
|
|
let remove = self.len().saturating_sub(len);
|
|
for link in self.links().take(remove) {
|
|
match link {
|
|
Link::Map(map) => suffix.apply(map),
|
|
Link::Barrier(_) => panic!("suffix contains barrier"),
|
|
}
|
|
}
|
|
suffix
|
|
}
|
|
|
|
/// Remove the last link from the chain.
|
|
pub fn pop(&mut self) {
|
|
*self = self.outer.copied().unwrap_or_default();
|
|
}
|
|
|
|
/// Return the span of a recipe for the given node.
|
|
pub(crate) fn recipe_span(self, node: TypeId) -> Option<Span> {
|
|
self.recipes(node).next().map(|recipe| recipe.span)
|
|
}
|
|
|
|
/// Return the chain, but without the last link if that one contains only
|
|
/// scoped styles. This is a hack.
|
|
pub(crate) fn unscoped(mut self, node: TypeId) -> Self {
|
|
if let Some(Link::Map(map)) = self.link {
|
|
if map.props.iter().all(|e| e.scoped && e.is_of_id(node))
|
|
&& map.recipes.is_empty()
|
|
{
|
|
self.pop();
|
|
}
|
|
}
|
|
self
|
|
}
|
|
}
|
|
|
|
impl<'a> StyleChain<'a> {
|
|
/// Get the (folded) value of a copyable style property.
|
|
///
|
|
/// This is the method you should reach for first. If it doesn't work
|
|
/// because your property is not copyable, use `get_ref`. If that doesn't
|
|
/// work either because your property needs folding, use `get_cloned`.
|
|
///
|
|
/// Returns the property's default value if no map in the chain contains an
|
|
/// entry for it.
|
|
pub fn get<P: Property>(self, key: P) -> P::Value
|
|
where
|
|
P::Value: Copy,
|
|
{
|
|
self.get_cloned(key)
|
|
}
|
|
|
|
/// Get a reference to a style property's value.
|
|
///
|
|
/// This is naturally only possible for properties that don't need folding.
|
|
/// Prefer `get` if possible or resort to `get_cloned` for non-`Copy`
|
|
/// properties that need folding.
|
|
///
|
|
/// Returns a lazily-initialized reference to the property's default value
|
|
/// if no map in the chain contains an entry for it.
|
|
pub fn get_ref<P: Property>(self, key: P) -> &'a P::Value
|
|
where
|
|
P: Nonfolding,
|
|
{
|
|
self.values(key).next().unwrap_or_else(|| P::default_ref())
|
|
}
|
|
|
|
/// Get the (folded) value of any style property.
|
|
///
|
|
/// While this works for all properties, you should prefer `get` or
|
|
/// `get_ref` where possible. This is only needed for non-`Copy` properties
|
|
/// that need folding.
|
|
///
|
|
/// Returns the property's default value if no map in the chain contains an
|
|
/// entry for it.
|
|
pub fn get_cloned<P: Property>(self, key: P) -> P::Value {
|
|
if P::FOLDING {
|
|
self.values(key)
|
|
.cloned()
|
|
.chain(std::iter::once(P::default()))
|
|
.reduce(P::fold)
|
|
.unwrap()
|
|
} else {
|
|
self.values(key).next().cloned().unwrap_or_else(P::default)
|
|
}
|
|
}
|
|
|
|
/// Execute a user recipe for a node.
|
|
pub fn show(
|
|
self,
|
|
node: &dyn Any,
|
|
vm: &mut Vm,
|
|
values: impl IntoIterator<Item = Value>,
|
|
) -> TypResult<Option<Template>> {
|
|
Ok(if let Some(recipe) = self.recipes(node.type_id()).next() {
|
|
let args = Args::from_values(recipe.span, values);
|
|
Some(recipe.func.call(vm, args)?.cast().at(recipe.span)?)
|
|
} else {
|
|
None
|
|
})
|
|
}
|
|
|
|
/// Insert a barrier into the style chain.
|
|
///
|
|
/// Barriers interact with [scoped](StyleMap::scoped) styles: A scoped style
|
|
/// can still be read through a single barrier (the one of the node it
|
|
/// _should_ apply to), but a second barrier will make it invisible.
|
|
pub fn barred<'b>(&'b self, node: TypeId) -> StyleChain<'b> {
|
|
if self
|
|
.maps()
|
|
.any(|map| map.props.iter().any(|entry| entry.scoped && entry.is_of_id(node)))
|
|
{
|
|
StyleChain {
|
|
link: Some(Link::Barrier(node)),
|
|
outer: Some(self),
|
|
}
|
|
} else {
|
|
*self
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> StyleChain<'a> {
|
|
/// Iterate over all values for the given property in the chain.
|
|
fn values<P: Property>(self, _: P) -> impl Iterator<Item = &'a P::Value> {
|
|
let mut depth = 0;
|
|
self.links().flat_map(move |link| {
|
|
let mut entries: &[Entry] = &[];
|
|
match link {
|
|
Link::Map(map) => entries = &map.props,
|
|
Link::Barrier(id) => depth += (id == P::node_id()) as usize,
|
|
}
|
|
entries
|
|
.iter()
|
|
.rev()
|
|
.filter(move |entry| entry.is::<P>() && (!entry.scoped || depth <= 1))
|
|
.filter_map(|entry| entry.downcast::<P>())
|
|
})
|
|
}
|
|
|
|
/// Iterate over the recipes for the given node.
|
|
fn recipes(self, node: TypeId) -> impl Iterator<Item = &'a Recipe> {
|
|
self.maps()
|
|
.flat_map(|map| map.recipes.iter().rev())
|
|
.filter(move |recipe| recipe.node == node)
|
|
}
|
|
|
|
/// Iterate over the map links of the chain.
|
|
fn maps(self) -> impl Iterator<Item = &'a StyleMap> {
|
|
self.links().filter_map(|link| match link {
|
|
Link::Map(map) => Some(map),
|
|
Link::Barrier(_) => None,
|
|
})
|
|
}
|
|
|
|
/// Iterate over the links of the chain.
|
|
fn links(self) -> impl Iterator<Item = Link<'a>> {
|
|
let mut cursor = Some(self);
|
|
std::iter::from_fn(move || {
|
|
let Self { link, outer } = cursor?;
|
|
cursor = outer.copied();
|
|
link
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Debug for StyleChain<'_> {
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
for link in self.links() {
|
|
link.fmt(f)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Debug for Link<'_> {
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
match self {
|
|
Self::Map(map) => map.fmt(f),
|
|
Self::Barrier(id) => writeln!(f, "Barrier({:?})", id),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PartialEq for StyleChain<'_> {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
let as_ptr = |s| s as *const _;
|
|
self.link == other.link && self.outer.map(as_ptr) == other.outer.map(as_ptr)
|
|
}
|
|
}
|
|
|
|
impl PartialEq for Link<'_> {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
match (*self, *other) {
|
|
(Self::Map(a), Self::Map(b)) => std::ptr::eq(a, b),
|
|
(Self::Barrier(a), Self::Barrier(b)) => a == b,
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A sequence of items with associated styles.
|
|
#[derive(Hash)]
|
|
pub struct StyleVec<T> {
|
|
items: Vec<T>,
|
|
maps: Vec<(StyleMap, usize)>,
|
|
}
|
|
|
|
impl<T> StyleVec<T> {
|
|
/// Whether there are any items in the sequence.
|
|
pub fn is_empty(&self) -> bool {
|
|
self.items.is_empty()
|
|
}
|
|
|
|
/// Iterate over the contained items.
|
|
pub fn items(&self) -> std::slice::Iter<'_, T> {
|
|
self.items.iter()
|
|
}
|
|
|
|
/// Iterate over the contained items and associated style maps.
|
|
pub fn iter(&self) -> impl Iterator<Item = (&T, &StyleMap)> + '_ {
|
|
let styles = self
|
|
.maps
|
|
.iter()
|
|
.flat_map(|(map, count)| std::iter::repeat(map).take(*count));
|
|
self.items().zip(styles)
|
|
}
|
|
}
|
|
|
|
impl<T> Default for StyleVec<T> {
|
|
fn default() -> Self {
|
|
Self { items: vec![], maps: vec![] }
|
|
}
|
|
}
|
|
|
|
impl<T: Debug> Debug for StyleVec<T> {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
f.debug_list()
|
|
.entries(self.iter().map(|(item, map)| {
|
|
crate::util::debug(|f| {
|
|
map.fmt(f)?;
|
|
item.fmt(f)
|
|
})
|
|
}))
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
/// Assists in the construction of a [`StyleVec`].
|
|
pub struct StyleVecBuilder<'a, T> {
|
|
items: Vec<T>,
|
|
chains: Vec<(StyleChain<'a>, usize)>,
|
|
}
|
|
|
|
impl<'a, T> StyleVecBuilder<'a, T> {
|
|
/// Create a new style-vec builder.
|
|
pub fn new() -> Self {
|
|
Self { items: vec![], chains: vec![] }
|
|
}
|
|
|
|
/// Push a new item into the style vector.
|
|
pub fn push(&mut self, item: T, styles: StyleChain<'a>) {
|
|
self.items.push(item);
|
|
|
|
if let Some((prev, count)) = self.chains.last_mut() {
|
|
if *prev == styles {
|
|
*count += 1;
|
|
return;
|
|
}
|
|
}
|
|
|
|
self.chains.push((styles, 1));
|
|
}
|
|
|
|
/// Access the last item mutably and its chain by value.
|
|
pub fn last_mut(&mut self) -> Option<(&mut T, StyleChain<'a>)> {
|
|
let item = self.items.last_mut()?;
|
|
let chain = self.chains.last()?.0;
|
|
Some((item, chain))
|
|
}
|
|
|
|
/// Finish building, returning a pair of two things:
|
|
/// - a style vector of items with the non-shared styles
|
|
/// - a shared prefix chain of styles that apply to all items
|
|
pub fn finish(self) -> (StyleVec<T>, StyleChain<'a>) {
|
|
let mut iter = self.chains.iter();
|
|
let mut trunk = match iter.next() {
|
|
Some(&(chain, _)) => chain,
|
|
None => return Default::default(),
|
|
};
|
|
|
|
let mut shared = trunk.len();
|
|
for &(mut chain, _) in iter {
|
|
let len = chain.len();
|
|
if len < shared {
|
|
for _ in 0 .. shared - len {
|
|
trunk.pop();
|
|
}
|
|
shared = len;
|
|
} else if len > shared {
|
|
for _ in 0 .. len - shared {
|
|
chain.pop();
|
|
}
|
|
}
|
|
|
|
while shared > 0 && chain != trunk {
|
|
trunk.pop();
|
|
chain.pop();
|
|
shared -= 1;
|
|
}
|
|
}
|
|
|
|
let maps = self
|
|
.chains
|
|
.into_iter()
|
|
.map(|(chain, count)| (chain.suffix(shared), count))
|
|
.collect();
|
|
|
|
(StyleVec { items: self.items, maps }, trunk)
|
|
}
|
|
}
|
|
|
|
impl<'a, T> Default for StyleVecBuilder<'a, T> {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|