summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-04-07 10:50:39 +0200
committerLaurenz <laurmaedje@gmail.com>2022-04-07 11:07:00 +0200
commit3d52387eea321e94c13b61666f7a758052b20c5d (patch)
tree5c55c51ca7e4b53dee61d280c39b7f664b8b9d6b /src
parent20b4d590b3efbd9b7a44fd6d3a658e7b84d21b99 (diff)
Rework style chains
Diffstat (limited to 'src')
-rw-r--r--src/eval/content.rs18
-rw-r--r--src/eval/func.rs3
-rw-r--r--src/eval/layout.rs23
-rw-r--r--src/eval/show.rs2
-rw-r--r--src/eval/styles.rs769
-rw-r--r--src/eval/value.rs4
-rw-r--r--src/library/graphics/image.rs2
-rw-r--r--src/library/graphics/shape.rs2
-rw-r--r--src/library/layout/flow.rs4
-rw-r--r--src/library/layout/page.rs6
-rw-r--r--src/library/math/mod.rs11
-rw-r--r--src/library/prelude.rs4
-rw-r--r--src/library/structure/heading.rs42
-rw-r--r--src/library/structure/list.rs12
-rw-r--r--src/library/structure/table.rs7
-rw-r--r--src/library/text/deco.rs10
-rw-r--r--src/library/text/link.rs13
-rw-r--r--src/library/text/mod.rs162
-rw-r--r--src/library/text/par.rs9
-rw-r--r--src/library/text/raw.rs20
-rw-r--r--src/library/text/shaping.rs16
21 files changed, 617 insertions, 522 deletions
diff --git a/src/eval/content.rs b/src/eval/content.rs
index 1cdd4bb0..01b43473 100644
--- a/src/eval/content.rs
+++ b/src/eval/content.rs
@@ -102,17 +102,15 @@ impl Content {
}
/// Style this content with a single style property.
- pub fn styled<P: Key>(mut self, key: P, value: P::Value) -> Self {
+ pub fn styled<'k, K: Key<'k>>(mut self, key: K, value: K::Value) -> Self {
if let Self::Styled(styled) = &mut self {
if let Some((_, map)) = Arc::get_mut(styled) {
- if !map.has_scoped() {
- map.set(key, value);
- return self;
- }
+ map.apply(key, value);
+ return self;
}
}
- self.styled_with_map(StyleMap::with(key, value))
+ Self::Styled(Arc::new((self, StyleMap::with(key, value))))
}
/// Style this content with a full style map.
@@ -123,10 +121,8 @@ impl Content {
if let Self::Styled(styled) = &mut self {
if let Some((_, map)) = Arc::get_mut(styled) {
- if !styles.has_scoped() && !map.has_scoped() {
- map.apply(&styles);
- return self;
- }
+ map.apply_map(&styles);
+ return self;
}
}
@@ -161,7 +157,7 @@ impl Content {
let tpa = Arena::new();
let styles = ctx.styles.clone();
- let styles = StyleChain::new(&styles);
+ let styles = StyleChain::with_root(&styles);
let mut builder = Builder::new(&sya, &tpa, true);
builder.process(ctx, self, styles)?;
diff --git a/src/eval/func.rs b/src/eval/func.rs
index 3eae453e..fcd19326 100644
--- a/src/eval/func.rs
+++ b/src/eval/func.rs
@@ -1,4 +1,3 @@
-use std::any::TypeId;
use std::fmt::{self, Debug, Formatter, Write};
use std::hash::{Hash, Hasher};
use std::sync::Arc;
@@ -52,7 +51,7 @@ impl Func {
show: if T::SHOWABLE {
Some(|recipe, span| {
let mut styles = StyleMap::new();
- styles.set_recipe(TypeId::of::<T>(), recipe, span);
+ styles.set_recipe::<T>(recipe, span);
styles
})
} else {
diff --git a/src/eval/layout.rs b/src/eval/layout.rs
index 94375c61..aecc7ef9 100644
--- a/src/eval/layout.rs
+++ b/src/eval/layout.rs
@@ -1,12 +1,12 @@
//! Layouting infrastructure.
-use std::any::{Any, TypeId};
+use std::any::Any;
use std::fmt::{self, Debug, Formatter};
use std::hash::Hash;
use std::sync::Arc;
+use super::{Barrier, StyleChain};
use crate::diag::TypResult;
-use crate::eval::StyleChain;
use crate::frame::{Element, Frame, Geometry, Shape, Stroke};
use crate::geom::{Align, Length, Linear, Paint, Point, Sides, Size, Spec, Transform};
use crate::library::graphics::MoveNode;
@@ -18,7 +18,7 @@ use crate::Context;
///
/// Layout return one frame per used region alongside constraints that define
/// whether the result is reusable in other regions.
-pub trait Layout {
+pub trait Layout: 'static {
/// Layout this node into the given regions, producing constrained frames.
fn layout(
&self,
@@ -144,12 +144,12 @@ impl LayoutNode {
/// Check whether the contained node is a specific layout node.
pub fn is<T: 'static>(&self) -> bool {
- self.0.as_any().is::<T>()
+ (**self.0).as_any().is::<T>()
}
- /// The type id of this node.
- pub fn id(&self) -> TypeId {
- self.0.as_any().type_id()
+ /// A barrier for the node.
+ pub fn barrier(&self) -> Barrier {
+ (**self.0).barrier()
}
/// Try to downcast to a specific layout node.
@@ -157,7 +157,7 @@ impl LayoutNode {
where
T: Layout + Debug + Hash + 'static,
{
- self.0.as_any().downcast_ref()
+ (**self.0).as_any().downcast_ref()
}
/// Force a size for this node.
@@ -223,7 +223,7 @@ impl Layout for LayoutNode {
styles: StyleChain,
) -> TypResult<Vec<Arc<Frame>>> {
ctx.query((self, regions, styles), |ctx, (node, regions, styles)| {
- node.0.layout(ctx, regions, styles.barred(node.id()))
+ node.0.layout(ctx, regions, node.barrier().chain(&styles))
})
.clone()
}
@@ -253,6 +253,7 @@ impl PartialEq for LayoutNode {
trait Bounds: Layout + Debug + Sync + Send + 'static {
fn as_any(&self) -> &dyn Any;
+ fn barrier(&self) -> Barrier;
}
impl<T> Bounds for T
@@ -262,6 +263,10 @@ where
fn as_any(&self) -> &dyn Any {
self
}
+
+ fn barrier(&self) -> Barrier {
+ Barrier::new::<T>()
+ }
}
/// A layout node that produces an empty frame.
diff --git a/src/eval/show.rs b/src/eval/show.rs
index e85903d2..383497ba 100644
--- a/src/eval/show.rs
+++ b/src/eval/show.rs
@@ -9,7 +9,7 @@ use crate::util::Prehashed;
use crate::Context;
/// A node that can be realized given some styles.
-pub trait Show {
+pub trait Show: 'static {
/// Realize this node in the given styles.
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content>;
diff --git a/src/eval/styles.rs b/src/eval/styles.rs
index a0dc263c..8bd21612 100644
--- a/src/eval/styles.rs
+++ b/src/eval/styles.rs
@@ -1,48 +1,49 @@
use std::any::{Any, TypeId};
use std::fmt::{self, Debug, Formatter};
-use std::hash::{Hash, Hasher};
+use std::hash::Hash;
+use std::marker::PhantomData;
use std::sync::Arc;
-use super::{Args, Content, Func, Span, Value};
+use super::{Args, Content, Func, Layout, Node, Span, Value};
use crate::diag::{At, TypResult};
use crate::library::layout::PageNode;
use crate::library::text::{FontFamily, ParNode, TextNode};
+use crate::util::Prehashed;
use crate::Context;
/// A map of style properties.
#[derive(Default, Clone, PartialEq, Hash)]
-pub struct StyleMap {
- /// Settable properties.
- props: Vec<Entry>,
- /// Show rule recipes.
- recipes: Vec<Recipe>,
-}
+pub struct StyleMap(Vec<Entry>);
impl StyleMap {
/// Create a new, empty style map.
pub fn new() -> Self {
- Self { props: vec![], recipes: vec![] }
+ Self::default()
}
/// Whether this map contains no styles.
pub fn is_empty(&self) -> bool {
- self.props.is_empty() && self.recipes.is_empty()
+ self.0.is_empty()
}
/// Create a style map from a single property-value pair.
- pub fn with<K: Key>(key: K, value: K::Value) -> Self {
+ pub fn with<'a, K: Key<'a>>(key: K, value: K::Value) -> Self {
let mut styles = Self::new();
styles.set(key, value);
styles
}
- /// Set the value for a style property.
- pub fn set<K: Key>(&mut self, key: K, value: K::Value) {
- self.props.push(Entry::new(key, value));
+ /// 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<'a, K: Key<'a>>(&mut self, key: K, value: K::Value) {
+ self.0.push(Entry::Property(Property::new(key, value)));
}
- /// Set a value for a style property if it is `Some(_)`.
- pub fn set_opt<K: Key>(&mut self, key: K, value: Option<K::Value>) {
+ /// Set an inner value for a style property if it is `Some(_)`.
+ pub fn set_opt<'a, K: Key<'a>>(&mut self, key: K, value: Option<K::Value>) {
if let Some(value) = value {
self.set(key, value);
}
@@ -50,167 +51,155 @@ impl StyleMap {
/// Set a font family composed of a preferred family and existing families
/// from a style chain.
- pub fn set_family(&mut self, family: FontFamily, existing: StyleChain) {
+ pub fn set_family(&mut self, preferred: FontFamily, existing: StyleChain) {
self.set(
TextNode::FAMILY,
- std::iter::once(family)
- .chain(existing.get_ref(TextNode::FAMILY).iter().cloned())
+ std::iter::once(preferred)
+ .chain(existing.get(TextNode::FAMILY).iter().cloned())
.collect(),
);
}
- /// 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)
+ /// Set a show rule recipe for a node.
+ pub fn set_recipe<T: Node>(&mut self, func: Func, span: Span) {
+ self.0.push(Entry::Recipe(Recipe::new::<T>(func, span)));
}
- /// Make `self` the first link of the style chain `outer`.
+ /// Make `self` the first link of the `tail` chain.
///
/// 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> {
+ /// `tail`. The ones from `self` take precedence over the ones from
+ /// `tail`. For folded properties `self` contributes the inner value.
+ pub fn chain<'a>(&'a self, tail: &'a StyleChain<'a>) -> StyleChain<'a> {
if self.is_empty() {
- *outer
+ *tail
} else {
- StyleChain {
- link: Some(Link::Map(self)),
- outer: Some(outer),
- }
+ StyleChain { head: &self.0, tail: Some(tail) }
}
}
- /// Apply styles from `outer` in-place. The resulting style map is
- /// equivalent to the style chain created by
- /// `self.chain(StyleChain::new(outer))`.
+ /// Set an outer value for a style property.
+ ///
+ /// If the property needs folding and the value is already contained in the
+ /// style map, `self` contributes the inner values and `value` is the outer
+ /// one.
+ ///
+ /// Like [`chain`](Self::chain) or [`apply_map`](Self::apply_map), but with
+ /// only a single property.
+ pub fn apply<'a, K: Key<'a>>(&mut self, key: K, value: K::Value) {
+ self.0.insert(0, Entry::Property(Property::new(key, value)));
+ }
+
+ /// Apply styles from `tail` in-place. The resulting style map is equivalent
+ /// to the style chain created by `self.chain(StyleChain::new(tail))`.
///
- /// 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());
+ /// This is useful over `chain` when you want to combine two maps, but you
+ /// still need an owned map without a lifetime.
+ pub fn apply_map(&mut self, tail: &Self) {
+ self.0.splice(0 .. 0, tail.0.iter().cloned());
+ }
+
+ /// 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 [constructors](Node::construct).
+ pub fn scoped(mut self) -> Self {
+ for entry in &mut self.0 {
+ if let Entry::Property(property) = entry {
+ property.scoped = true;
+ }
+ }
+ self
}
- /// The highest-level interruption of the map.
+ /// The highest-level kind of of structure the map interrupts.
pub fn interruption(&self) -> Option<Interruption> {
- self.props.iter().filter_map(|entry| entry.interruption()).max()
+ self.0
+ .iter()
+ .filter_map(|entry| entry.property())
+ .filter_map(|property| property.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)?;
+ for entry in self.0.iter().rev() {
+ writeln!(f, "{:?}", entry)?;
}
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,
+/// An entry for a single style property, recipe or barrier.
+#[derive(Clone, PartialEq, Hash)]
+enum Entry {
+ /// A style property originating from a set rule or constructor.
+ Property(Property),
+ /// A barrier for scoped styles.
+ Barrier(TypeId, &'static str),
+ /// A show rule recipe.
+ Recipe(Recipe),
}
-/// Style property keys.
-///
-/// This trait is not intended to be implemented manually, but rather through
-/// the `#[node]` proc-macro.
-pub trait Key: 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;
+impl Entry {
+ /// If this is a property, return it.
+ fn property(&self) -> Option<&Property> {
+ match self {
+ Self::Property(property) => Some(property),
+ _ => None,
+ }
+ }
- /// A static reference to the default value of the property.
- ///
- /// This is automatically implemented through lazy-initialization in the
- /// `#[node]` macro. This way, expensive defaults don't need to be
- /// recreated all the time.
- fn default_ref() -> &'static Self::Value;
+ /// If this is a recipe, return it.
+ fn recipe(&self) -> Option<&Recipe> {
+ match self {
+ Self::Recipe(recipe) => Some(recipe),
+ _ => None,
+ }
+ }
+}
- /// 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
+impl Debug for Entry {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.write_str("#[")?;
+ match self {
+ Self::Property(property) => property.fmt(f)?,
+ Self::Recipe(recipe) => recipe.fmt(f)?,
+ Self::Barrier(_, name) => write!(f, "Barrier for {name}")?,
+ }
+ f.write_str("]")
}
}
-/// Marker trait indicating that a property can be accessed by reference.
-///
-/// This is implemented by a key if and only if `K::FOLDING` if false.
-/// Unfortunately, Rust's type system doesn't allow use to use an associated
-/// constant to bound a function, so we need this trait.
-pub trait Referencable {}
-
-/// An entry for a single style property.
-#[derive(Clone)]
-struct Entry {
- pair: Arc<dyn Bounds>,
+/// A style property originating from a set rule or constructor.
+#[derive(Clone, Hash)]
+struct Property {
+ /// The type id of the property's [key](Key).
+ key: TypeId,
+ /// The type id of the node the property belongs to.
+ node: TypeId,
+ /// The name of the property.
+ name: &'static str,
+ /// The property's value.
+ value: Arc<Prehashed<dyn Bounds>>,
+ /// Whether the property should only affects the first node down the
+ /// hierarchy. Used by constructors.
scoped: bool,
}
-impl Entry {
- fn new<K: Key>(key: K, value: K::Value) -> Self {
+impl Property {
+ /// Create a new property from a key-value pair.
+ fn new<'a, K: Key<'a>>(_: K, value: K::Value) -> Self {
Self {
- pair: Arc::new((key, value)),
+ key: TypeId::of::<K>(),
+ node: K::node(),
+ name: K::NAME,
+ value: Arc::new(Prehashed::new(value)),
scoped: false,
}
}
- fn is<P: Key>(&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<K: Key>(&self) -> Option<&K::Value> {
- self.pair.as_any().downcast_ref()
- }
-
+ /// What kind of structure the property interrupts.
fn interruption(&self) -> Option<Interruption> {
if self.is_of::<PageNode>() {
Some(Interruption::Page)
@@ -220,320 +209,370 @@ impl Entry {
None
}
}
+
+ /// Access the property's value if it is of the given key.
+ fn downcast<'a, K: Key<'a>>(&'a self) -> Option<&'a K::Value> {
+ if self.key == TypeId::of::<K>() {
+ (**self.value).as_any().downcast_ref()
+ } else {
+ None
+ }
+ }
+
+ /// Whether this property belongs to the node `T`.
+ fn is_of<T: Node>(&self) -> bool {
+ self.node == TypeId::of::<T>()
+ }
}
-impl Debug for Entry {
+impl Debug for Property {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.write_str("#[")?;
- self.pair.dyn_fmt(f)?;
+ write!(f, "{} = {:?}", self.name, self.value)?;
if self.scoped {
- f.write_str(" (scoped)")?;
+ write!(f, " [scoped]")?;
}
- f.write_str("]")
+ Ok(())
}
}
-impl PartialEq for Entry {
+impl PartialEq for Property {
fn eq(&self, other: &Self) -> bool {
- self.pair.dyn_eq(other) && self.scoped == other.scoped
+ self.key == other.key
+ && self.value.eq(&other.value)
+ && 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 {
+trait Bounds: Debug + 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<K: Key> Bounds for (K, K::Value) {
+impl<T> Bounds for T
+where
+ T: Debug + Sync + Send + 'static,
+{
fn as_any(&self) -> &dyn Any {
- &self.1
+ self
}
+}
- fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(f, "{} = {:?}", K::NAME, self.1)
- }
+/// Style property keys.
+///
+/// This trait is not intended to be implemented manually, but rather through
+/// the `#[node]` proc-macro.
+pub trait Key<'a>: 'static {
+ /// The unfolded type which this property is stored as in a style map. For
+ /// example, this is [`Toggle`](crate::geom::Length) for the
+ /// [`STRONG`](TextNode::STRONG) property.
+ type Value: Debug + Clone + Hash + Sync + Send + 'static;
+
+ /// The folded type of value that is returned when reading this property
+ /// from a style chain. For example, this is [`bool`] for the
+ /// [`STRONG`](TextNode::STRONG) property. For non-copy, non-folding
+ /// properties this is a reference type.
+ type Output: 'a;
- fn dyn_eq(&self, other: &Entry) -> bool {
- self.style_id() == other.pair.style_id()
- && if let Some(other) = other.downcast::<K>() {
- &self.1 == other
- } else {
- false
- }
- }
+ /// The name of the property, used for debug printing.
+ const NAME: &'static str;
- fn hash64(&self) -> u64 {
- let mut state = fxhash::FxHasher64::default();
- self.style_id().hash(&mut state);
- self.1.hash(&mut state);
- state.finish()
- }
+ /// The type id of the node this property belongs to.
+ fn node() -> TypeId;
- fn node_id(&self) -> TypeId {
- K::node_id()
- }
+ /// Compute an output value from a sequence of values belong to this key,
+ /// folding if necessary.
+ fn get(values: impl Iterator<Item = &'a Self::Value>) -> Self::Output;
+}
- fn style_id(&self) -> TypeId {
- TypeId::of::<K>()
- }
+/// A property that is folded to determine its final value.
+pub trait Fold {
+ /// The type of the folded output.
+ type Output;
+
+ /// Fold this inner value with an outer folded value.
+ fn fold(self, outer: Self::Output) -> Self::Output;
}
/// A show rule recipe.
-#[derive(Debug, Clone, PartialEq, Hash)]
+#[derive(Clone, PartialEq, Hash)]
struct Recipe {
+ /// The affected node.
node: TypeId,
+ /// The name of the affected node.
+ name: &'static str,
+ /// The function that defines the recipe.
func: Func,
+ /// The span to report all erros with.
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 Recipe {
+ /// Create a new recipe for the node `T`.
+ fn new<T: Node>(func: Func, span: Span) -> Self {
+ Self {
+ node: TypeId::of::<T>(),
+ name: std::any::type_name::<T>(),
+ func,
+ span,
+ }
+ }
}
-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 }
+impl Debug for Recipe {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "Recipe for {} from {:?}", self.name, self.span)
}
+}
- /// The number of links in the chain.
- pub fn len(self) -> usize {
- self.links().count()
- }
+/// A style chain barrier.
+///
+/// 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.
+#[derive(Clone, PartialEq, Hash)]
+pub struct Barrier(Entry);
+
+impl Barrier {
+ /// Create a new barrier for the layout node `T`.
+ pub fn new<T: Layout>() -> Self {
+ Self(Entry::Barrier(
+ TypeId::of::<T>(),
+ std::any::type_name::<T>(),
+ ))
+ }
+
+ /// Make this barrier the first link of the `tail` chain.
+ pub fn chain<'a>(&'a self, tail: &'a StyleChain) -> StyleChain<'a> {
+ // We have to store a full `Entry` enum inside the barrier because
+ // otherwise the `slice::from_ref` trick below won't work.
+ // Unfortunately, that also means we have to somehow extract the id
+ // here.
+ let id = match self.0 {
+ Entry::Barrier(id, _) => id,
+ _ => unreachable!(),
+ };
- /// 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"),
+ if tail
+ .entries()
+ .filter_map(Entry::property)
+ .any(|p| p.scoped && p.node == id)
+ {
+ StyleChain {
+ head: std::slice::from_ref(&self.0),
+ tail: Some(tail),
}
+ } else {
+ *tail
}
- suffix
}
+}
- /// Remove the last link from the chain.
- pub fn pop(&mut self) {
- *self = self.outer.copied().unwrap_or_default();
- }
-
- /// 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 Debug for Barrier {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ self.0.fmt(f)
}
}
+/// 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,
+}
+
+/// A chain of style maps, similar to a linked list.
+///
+/// A style chain allows to combine 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.
+ head: &'a [Entry],
+ /// The remaining links in the chain.
+ tail: Option<&'a 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<K: Key>(self, key: K) -> K::Value
- where
- K::Value: Copy,
- {
- self.get_cloned(key)
+ /// Create a new, empty style chain.
+ pub fn new() -> Self {
+ Self::default()
}
- /// 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<K: Key>(self, key: K) -> &'a K::Value
- where
- K: Referencable,
- {
- self.values(key).next().unwrap_or_else(|| K::default_ref())
+ /// Start a new style chain with a root map.
+ pub fn with_root(root: &'a StyleMap) -> Self {
+ Self { head: &root.0, tail: None }
}
- /// 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.
+ /// Get the output value of a style property.
///
/// Returns the property's default value if no map in the chain contains an
- /// entry for it.
- pub fn get_cloned<K: Key>(self, key: K) -> K::Value {
- if K::FOLDING {
- self.values(key)
- .cloned()
- .chain(std::iter::once(K::default()))
- .reduce(K::fold)
- .unwrap()
- } else {
- self.values(key).next().cloned().unwrap_or_else(K::default)
- }
+ /// entry for it. Also takes care of folding and returns references where
+ /// applicable.
+ pub fn get<K: Key<'a>>(self, key: K) -> K::Output {
+ K::get(self.values(key))
}
- /// Execute a user recipe for a node.
- pub fn show(
- self,
- node: &dyn Any,
- ctx: &mut Context,
- values: impl IntoIterator<Item = Value>,
- ) -> TypResult<Option<Content>> {
- Ok(if let Some(recipe) = self.recipes(node.type_id()).next() {
+ /// Execute and return the result of a user recipe for a node if there is
+ /// any.
+ pub fn show<T, I>(self, ctx: &mut Context, values: I) -> TypResult<Option<Content>>
+ where
+ T: Node,
+ I: IntoIterator<Item = Value>,
+ {
+ if let Some(recipe) = self
+ .entries()
+ .filter_map(Entry::recipe)
+ .find(|recipe| recipe.node == TypeId::of::<T>())
+ {
let args = Args::from_values(recipe.span, values);
- Some(recipe.func.call(ctx, args)?.cast().at(recipe.span)?)
+ Ok(Some(recipe.func.call(ctx, args)?.cast().at(recipe.span)?))
} else {
- None
- })
+ Ok(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)))
+impl<'a> StyleChain<'a> {
+ /// Return the chain, but without the trailing scoped property for the given
+ /// `node`. This is a 90% hack fix for show node constructor scoping.
+ pub(super) fn unscoped(mut self, node: TypeId) -> Self {
+ while self
+ .head
+ .last()
+ .and_then(Entry::property)
+ .map_or(false, |p| p.scoped && p.node == node)
{
- StyleChain {
- link: Some(Link::Barrier(node)),
- outer: Some(self),
- }
- } else {
- *self
+ let len = self.head.len();
+ self.head = &self.head[.. len - 1]
}
+ self
}
-}
-impl<'a> StyleChain<'a> {
- /// Iterate over all values for the given property in the chain.
- fn values<K: Key>(self, _: K) -> impl Iterator<Item = &'a K::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 == K::node_id()) as usize,
- }
- entries
- .iter()
- .rev()
- .filter(move |entry| entry.is::<K>() && (!entry.scoped || depth <= 1))
- .filter_map(|entry| entry.downcast::<K>())
- })
+ /// Remove the last link from the chain.
+ fn pop(&mut self) {
+ *self = self.tail.copied().unwrap_or_default();
+ }
+
+ /// Build a style map from the suffix (all links beyond the `len`) of the
+ /// chain.
+ fn suffix(self, len: usize) -> StyleMap {
+ let mut suffix = StyleMap::new();
+ let take = self.links().count().saturating_sub(len);
+ for link in self.links().take(take) {
+ suffix.0.splice(0 .. 0, link.iter().cloned());
+ }
+ suffix
}
- /// 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 all values for the given property in the chain.
+ fn values<K: Key<'a>>(self, _: K) -> Values<'a, K> {
+ Values {
+ entries: self.entries(),
+ depth: 0,
+ key: PhantomData,
+ }
}
- /// 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 entries of the chain.
+ fn entries(self) -> Entries<'a> {
+ Entries {
+ inner: [].as_slice().iter(),
+ links: self.links(),
+ }
}
/// 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
- })
+ fn links(self) -> Links<'a> {
+ Links(Some(self))
}
}
impl Debug for StyleChain<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- for link in self.links() {
- link.fmt(f)?;
+ for entry in self.entries() {
+ writeln!(f, "{:?}", entry)?;
}
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)
+ self.head.as_ptr() == other.head.as_ptr()
+ && self.head.len() == other.head.len()
+ && self.tail.map(as_ptr) == other.tail.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,
+/// An iterator over the values in a style chain.
+struct Values<'a, K> {
+ entries: Entries<'a>,
+ depth: usize,
+ key: PhantomData<K>,
+}
+
+impl<'a, K: Key<'a>> Iterator for Values<'a, K> {
+ type Item = &'a K::Value;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ while let Some(entry) = self.entries.next() {
+ match entry {
+ Entry::Property(property) => {
+ if let Some(value) = property.downcast::<K>() {
+ if !property.scoped || self.depth <= 1 {
+ return Some(value);
+ }
+ }
+ }
+ Entry::Barrier(id, _) => {
+ self.depth += (*id == K::node()) as usize;
+ }
+ Entry::Recipe(_) => {}
+ }
}
+
+ None
+ }
+}
+
+/// An iterator over the entries in a style chain.
+struct Entries<'a> {
+ inner: std::slice::Iter<'a, Entry>,
+ links: Links<'a>,
+}
+
+impl<'a> Iterator for Entries<'a> {
+ type Item = &'a Entry;
+
+ 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.
+struct Links<'a>(Option<StyleChain<'a>>);
+
+impl<'a> Iterator for Links<'a> {
+ type Item = &'a [Entry];
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let StyleChain { head, tail } = self.0?;
+ self.0 = tail.copied();
+ Some(head)
}
}
@@ -648,9 +687,9 @@ impl<'a, T> StyleVecBuilder<'a, T> {
None => return Default::default(),
};
- let mut shared = trunk.len();
+ let mut shared = trunk.links().count();
for &(mut chain, _) in iter {
- let len = chain.len();
+ let len = chain.links().count();
if len < shared {
for _ in 0 .. shared - len {
trunk.pop();
diff --git a/src/eval/value.rs b/src/eval/value.rs
index 300444de..c61c9561 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -359,12 +359,12 @@ impl Dynamic {
/// Whether the wrapped type is `T`.
pub fn is<T: Type + 'static>(&self) -> bool {
- self.0.as_any().is::<T>()
+ (*self.0).as_any().is::<T>()
}
/// Try to downcast to a reference to a specific type.
pub fn downcast<T: Type + 'static>(&self) -> Option<&T> {
- self.0.as_any().downcast_ref()
+ (*self.0).as_any().downcast_ref()
}
/// The name of the stored value's type.
diff --git a/src/library/graphics/image.rs b/src/library/graphics/image.rs
index d11de9d1..23ad52ab 100644
--- a/src/library/graphics/image.rs
+++ b/src/library/graphics/image.rs
@@ -82,7 +82,7 @@ impl Layout for ImageNode {
}
// Apply link if it exists.
- if let Some(url) = styles.get_ref(TextNode::LINK) {
+ if let Some(url) = styles.get(TextNode::LINK) {
frame.link(url);
}
diff --git a/src/library/graphics/shape.rs b/src/library/graphics/shape.rs
index 9f9ff889..177f466e 100644
--- a/src/library/graphics/shape.rs
+++ b/src/library/graphics/shape.rs
@@ -132,7 +132,7 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
}
// Apply link if it exists.
- if let Some(url) = styles.get_ref(TextNode::LINK) {
+ if let Some(url) = styles.get(TextNode::LINK) {
frame.link(url);
}
diff --git a/src/library/layout/flow.rs b/src/library/layout/flow.rs
index 3602bea6..9f398277 100644
--- a/src/library/layout/flow.rs
+++ b/src/library/layout/flow.rs
@@ -37,12 +37,12 @@ impl Layout for FlowNode {
let styles = map.chain(&styles);
match child {
FlowChild::Leading => {
- let em = styles.get(TextNode::SIZE).abs;
+ let em = styles.get(TextNode::SIZE);
let amount = styles.get(ParNode::LEADING).resolve(em);
layouter.layout_spacing(amount.into());
}
FlowChild::Parbreak => {
- let em = styles.get(TextNode::SIZE).abs;
+ let em = styles.get(TextNode::SIZE);
let leading = styles.get(ParNode::LEADING);
let spacing = styles.get(ParNode::SPACING);
let amount = (leading + spacing).resolve(em);
diff --git a/src/library/layout/page.rs b/src/library/layout/page.rs
index b1008feb..c8af4843 100644
--- a/src/library/layout/page.rs
+++ b/src/library/layout/page.rs
@@ -28,8 +28,10 @@ impl PageNode {
/// How many columns the page has.
pub const COLUMNS: NonZeroUsize = NonZeroUsize::new(1).unwrap();
/// The page's header.
+ #[property(referenced)]
pub const HEADER: Marginal = Marginal::None;
/// The page's footer.
+ #[property(referenced)]
pub const FOOTER: Marginal = Marginal::None;
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
@@ -116,8 +118,8 @@ impl PageNode {
let regions = Regions::repeat(size, size, size.map(Length::is_finite));
let mut frames = child.layout(ctx, &regions, styles)?;
- let header = styles.get_ref(Self::HEADER);
- let footer = styles.get_ref(Self::FOOTER);
+ let header = styles.get(Self::HEADER);
+ let footer = styles.get(Self::FOOTER);
// Realize header and footer.
for frame in &mut frames {
diff --git a/src/library/math/mod.rs b/src/library/math/mod.rs
index ddd80435..e6548438 100644
--- a/src/library/math/mod.rs
+++ b/src/library/math/mod.rs
@@ -15,6 +15,7 @@ pub struct MathNode {
#[node(showable)]
impl MathNode {
/// The raw text's font family. Just the normal text family if `auto`.
+ #[property(referenced)]
pub const FAMILY: Smart<FontFamily> =
Smart::Custom(FontFamily::new("Latin Modern Math"));
@@ -28,16 +29,14 @@ impl MathNode {
impl Show for MathNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
+ let args = [Value::Str(self.formula.clone()), Value::Bool(self.display)];
let mut content = styles
- .show(self, ctx, [
- Value::Str(self.formula.clone()),
- Value::Bool(self.display),
- ])?
+ .show::<Self, _>(ctx, args)?
.unwrap_or_else(|| Content::Text(self.formula.trim().into()));
let mut map = StyleMap::new();
- if let Smart::Custom(family) = styles.get_cloned(Self::FAMILY) {
- map.set_family(family, styles);
+ if let Smart::Custom(family) = styles.get(Self::FAMILY) {
+ map.set_family(family.clone(), styles);
}
content = content.styled_with_map(map);
diff --git a/src/library/prelude.rs b/src/library/prelude.rs
index 39be5994..a2e296fa 100644
--- a/src/library/prelude.rs
+++ b/src/library/prelude.rs
@@ -9,8 +9,8 @@ pub use typst_macros::node;
pub use crate::diag::{with_alternative, At, Error, StrResult, TypError, TypResult};
pub use crate::eval::{
- Arg, Args, Array, Cast, Content, Dict, Func, Key, Layout, LayoutNode, Merge, Node,
- Regions, Scope, Show, ShowNode, Smart, StyleChain, StyleMap, StyleVec, Value,
+ Arg, Args, Array, Cast, Content, Dict, Fold, Func, Key, Layout, LayoutNode, Merge,
+ Node, Regions, Scope, Show, ShowNode, Smart, StyleChain, StyleMap, StyleVec, Value,
};
pub use crate::frame::*;
pub use crate::geom::*;
diff --git a/src/library/structure/heading.rs b/src/library/structure/heading.rs
index 7d3273f5..7b00c643 100644
--- a/src/library/structure/heading.rs
+++ b/src/library/structure/heading.rs
@@ -1,5 +1,5 @@
use crate::library::prelude::*;
-use crate::library::text::{FontFamily, TextNode};
+use crate::library::text::{FontFamily, FontSize, TextNode, Toggle};
/// A section heading.
#[derive(Debug, Hash)]
@@ -14,25 +14,34 @@ pub struct HeadingNode {
#[node(showable)]
impl HeadingNode {
/// The heading's font family. Just the normal text family if `auto`.
+ #[property(referenced)]
pub const FAMILY: Leveled<Smart<FontFamily>> = Leveled::Value(Smart::Auto);
/// The color of text in the heading. Just the normal text color if `auto`.
+ #[property(referenced)]
pub const FILL: Leveled<Smart<Paint>> = Leveled::Value(Smart::Auto);
/// The size of text in the heading.
- pub const SIZE: Leveled<Linear> = Leveled::Mapping(|level| {
+ #[property(referenced)]
+ pub const SIZE: Leveled<FontSize> = Leveled::Mapping(|level| {
let upscale = (1.6 - 0.1 * level as f64).max(0.75);
- Relative::new(upscale).into()
+ FontSize(Relative::new(upscale).into())
});
/// Whether text in the heading is strengthend.
+ #[property(referenced)]
pub const STRONG: Leveled<bool> = Leveled::Value(true);
/// Whether text in the heading is emphasized.
+ #[property(referenced)]
pub const EMPH: Leveled<bool> = Leveled::Value(false);
/// Whether the heading is underlined.
+ #[property(referenced)]
pub const UNDERLINE: Leveled<bool> = Leveled::Value(false);
/// The extra padding above the heading.
+ #[property(referenced)]
pub const ABOVE: Leveled<Length> = Leveled::Value(Length::zero());
/// The extra padding below the heading.
+ #[property(referenced)]
pub const BELOW: Leveled<Length> = Leveled::Value(Length::zero());
/// Whether the heading is block-level.
+ #[property(referenced)]
pub const BLOCK: Leveled<bool> = Leveled::Value(true);
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
@@ -47,16 +56,17 @@ impl Show for HeadingNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
macro_rules! resolve {
($key:expr) => {
- styles.get_cloned($key).resolve(ctx, self.level)?
+ styles.get($key).resolve(ctx, self.level)?
};
}
- // Resolve the user recipe.
+ let args = [
+ Value::Int(self.level as i64),
+ Value::Content(self.body.clone()),
+ ];
+
let mut body = styles
- .show(self, ctx, [
- Value::Int(self.level as i64),
- Value::Content(self.body.clone()),
- ])?
+ .show::<Self, _>(ctx, args)?
.unwrap_or_else(|| self.body.clone());
let mut map = StyleMap::new();
@@ -71,11 +81,11 @@ impl Show for HeadingNode {
}
if resolve!(Self::STRONG) {
- map.set(TextNode::STRONG, true);
+ map.set(TextNode::STRONG, Toggle);
}
if resolve!(Self::EMPH) {
- map.set(TextNode::EMPH, true);
+ map.set(TextNode::EMPH, Toggle);
}
let mut seq = vec![];
@@ -116,15 +126,15 @@ pub enum Leveled<T> {
Func(Func, Span),
}
-impl<T: Cast> Leveled<T> {
+impl<T: Cast + Clone> Leveled<T> {
/// Resolve the value based on the level.
- pub fn resolve(self, ctx: &mut Context, level: usize) -> TypResult<T> {
+ pub fn resolve(&self, ctx: &mut Context, level: usize) -> TypResult<T> {
Ok(match self {
- Self::Value(value) => value,
+ Self::Value(value) => value.clone(),
Self::Mapping(mapping) => mapping(level),
Self::Func(func, span) => {
- let args = Args::from_values(span, [Value::Int(level as i64)]);
- func.call(ctx, args)?.cast().at(span)?
+ let args = Args::from_values(*span, [Value::Int(level as i64)]);
+ func.call(ctx, args)?.cast().at(*span)?
}
})
}
diff --git a/src/library/structure/list.rs b/src/library/structure/list.rs
index 414f601e..1b22e166 100644
--- a/src/library/structure/list.rs
+++ b/src/library/structure/list.rs
@@ -31,6 +31,7 @@ pub type EnumNode = ListNode<ORDERED>;
#[node(showable)]
impl<const L: ListKind> ListNode<L> {
/// How the list is labelled.
+ #[property(referenced)]
pub const LABEL: Label = Label::Default;
/// The spacing between the list items of a non-wide list.
pub const SPACING: Linear = Linear::zero();
@@ -58,17 +59,14 @@ impl<const L: ListKind> ListNode<L> {
impl<const L: ListKind> Show for ListNode<L> {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
- let content = if let Some(content) = styles.show(
- self,
- ctx,
- self.items.iter().map(|item| Value::Content((*item.body).clone())),
- )? {
+ let args = self.items.iter().map(|item| Value::Content((*item.body).clone()));
+ let content = if let Some(content) = styles.show::<Self, _>(ctx, args)? {
content
} else {
let mut children = vec![];
let mut number = self.start;
- let label = styles.get_ref(Self::LABEL);
+ let label = styles.get(Self::LABEL);
for item in &self.items {
number = item.number.unwrap_or(number);
@@ -79,7 +77,7 @@ impl<const L: ListKind> Show for ListNode<L> {
number += 1;
}
- let em = styles.get(TextNode::SIZE).abs;
+ let em = styles.get(TextNode::SIZE);
let leading = styles.get(ParNode::LEADING);
let spacing = if self.wide {
styles.get(ParNode::SPACING)
diff --git a/src/library/structure/table.rs b/src/library/structure/table.rs
index 0e455ead..64785006 100644
--- a/src/library/structure/table.rs
+++ b/src/library/structure/table.rs
@@ -55,11 +55,8 @@ impl TableNode {
impl Show for TableNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
- if let Some(content) = styles.show(
- self,
- ctx,
- self.children.iter().map(|child| Value::Content(child.clone())),
- )? {
+ let args = self.children.iter().map(|child| Value::Content(child.clone()));
+ if let Some(content) = styles.show::<Self, _>(ctx, args)? {
return Ok(content);
}
diff --git a/src/library/text/deco.rs b/src/library/text/deco.rs
index b98eb0b2..a6375e4e 100644
--- a/src/library/text/deco.rs
+++ b/src/library/text/deco.rs
@@ -21,11 +21,11 @@ pub type OverlineNode = DecoNode<OVERLINE>;
#[node(showable)]
impl<const L: DecoLine> DecoNode<L> {
/// Stroke color of the line, defaults to the text color if `None`.
- #[shorthand]
+ #[property(shorthand)]
pub const STROKE: Option<Paint> = None;
/// Thickness of the line's strokes (dependent on scaled font size), read
/// from the font tables if `None`.
- #[shorthand]
+ #[property(shorthand)]
pub const THICKNESS: Option<Linear> = None;
/// Position of the line relative to the baseline (dependent on scaled font
/// size), read from the font tables if `None`.
@@ -45,16 +45,16 @@ impl<const L: DecoLine> DecoNode<L> {
impl<const L: DecoLine> Show for DecoNode<L> {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
Ok(styles
- .show(self, ctx, [Value::Content(self.0.clone())])?
+ .show::<Self, _>(ctx, [Value::Content(self.0.clone())])?
.unwrap_or_else(|| {
- self.0.clone().styled(TextNode::LINES, vec![Decoration {
+ self.0.clone().styled(TextNode::DECO, Decoration {
line: L,
stroke: styles.get(Self::STROKE),
thickness: styles.get(Self::THICKNESS),
offset: styles.get(Self::OFFSET),
extent: styles.get(Self::EXTENT),
evade: styles.get(Self::EVADE),
- }])
+ })
}))
}
}
diff --git a/src/library/text/link.rs b/src/library/text/link.rs
index 4c2b5e7b..3ef7011d 100644
--- a/src/library/text/link.rs
+++ b/src/library/text/link.rs
@@ -29,14 +29,13 @@ impl LinkNode {
impl Show for LinkNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
+ let args = [Value::Str(self.url.clone()), match &self.body {
+ Some(body) => Value::Content(body.clone()),
+ None => Value::None,
+ }];
+
let mut body = styles
- .show(self, ctx, [
- Value::Str(self.url.clone()),
- match &self.body {
- Some(body) => Value::Content(body.clone()),
- None => Value::None,
- },
- ])?
+ .show::<Self, _>(ctx, args)?
.or_else(|| self.body.clone())
.unwrap_or_else(|| {
let url = &self.url;
diff --git a/src/library/text/mod.rs b/src/library/text/mod.rs
index 2c163a59..64994136 100644
--- a/src/library/text/mod.rs
+++ b/src/library/text/mod.rs
@@ -13,7 +13,6 @@ pub use raw::*;
pub use shaping::*;
use std::borrow::Cow;
-use std::ops::BitXor;
use ttf_parser::Tag;
@@ -28,7 +27,7 @@ pub struct TextNode;
#[node]
impl TextNode {
/// A prioritized sequence of font families.
- #[variadic]
+ #[property(referenced, variadic)]
pub const FAMILY: Vec<FontFamily> = vec![FontFamily::new("IBM Plex Sans")];
/// Whether to allow font fallback when the primary font list contains no
/// match.
@@ -40,14 +39,13 @@ impl TextNode {
pub const WEIGHT: FontWeight = FontWeight::REGULAR;
/// The width of the glyphs.
pub const STRETCH: FontStretch = FontStretch::NORMAL;
+ /// The size of the glyphs.
+ #[property(shorthand, fold)]
+ pub const SIZE: FontSize = Length::pt(11.0);
/// The glyph fill color.
- #[shorthand]
+ #[property(shorthand)]
pub const FILL: Paint = Color::BLACK.into();
- /// The size of the glyphs.
- #[shorthand]
- #[fold(Linear::compose)]
- pub const SIZE: Linear = Length::pt(11.0).into();
/// The amount of space that should be added between characters.
pub const TRACKING: Em = Em::zero();
/// The ratio by which spaces should be stretched.
@@ -84,26 +82,24 @@ impl TextNode {
/// Whether to convert fractions. ("frac")
pub const FRACTIONS: bool = false;
/// Raw OpenType features to apply.
+ #[property(fold)]
pub const FEATURES: Vec<(Tag, u32)> = vec![];
/// Whether the font weight should be increased by 300.
- #[skip]
- #[fold(bool::bitxor)]
- pub const STRONG: bool = false;
+ #[property(hidden, fold)]
+ pub const STRONG: Toggle = false;
/// Whether the the font style should be inverted.
- #[skip]
- #[fold(bool::bitxor)]
- pub const EMPH: bool = false;
- /// The case transformation that should be applied to the next.
- #[skip]
+ #[property(hidden, fold)]
+ pub const EMPH: Toggle = false;
+ /// A case transformation that should be applied to the text.
+ #[property(hidden)]
pub const CASE: Option<Case> = None;
- /// Decorative lines.
- #[skip]
- #[fold(|a, b| a.into_iter().chain(b).collect())]
- pub const LINES: Vec<Decoration> = vec![];
/// An URL the text should link to.
- #[skip]
+ #[property(hidden, referenced)]
pub const LINK: Option<EcoString> = None;
+ /// Decorative lines.
+ #[property(hidden, fold)]
+ pub const DECO: Decoration = vec![];
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
// The text constructor is special: It doesn't create a text node.
@@ -113,44 +109,6 @@ impl TextNode {
}
}
-/// Strong text, rendered in boldface.
-#[derive(Debug, Hash)]
-pub struct StrongNode(pub Content);
-
-#[node(showable)]
-impl StrongNode {
- fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
- Ok(Content::show(Self(args.expect("body")?)))
- }
-}
-
-impl Show for StrongNode {
- fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
- Ok(styles
- .show(self, ctx, [Value::Content(self.0.clone())])?
- .unwrap_or_else(|| self.0.clone().styled(TextNode::STRONG, true)))
- }
-}
-
-/// Emphasized text, rendered with an italic face.
-#[derive(Debug, Hash)]
-pub struct EmphNode(pub Content);
-
-#[node(showable)]
-impl EmphNode {
- fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
- Ok(Content::show(Self(args.expect("body")?)))
- }
-}
-
-impl Show for EmphNode {
- fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
- Ok(styles
- .show(self, ctx, [Value::Content(self.0.clone())])?
- .unwrap_or_else(|| self.0.clone().styled(TextNode::EMPH, true)))
- }
-}
-
/// A font family like "Arial".
#[derive(Clone, Eq, PartialEq, Hash)]
pub struct FontFamily(EcoString);
@@ -228,6 +186,26 @@ castable! {
Value::Relative(v) => Self::from_ratio(v.get() as f32),
}
+/// The size of text.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub struct FontSize(pub Linear);
+
+impl Fold for FontSize {
+ type Output = Length;
+
+ fn fold(self, outer: Self::Output) -> Self::Output {
+ self.0.rel.resolve(outer) + self.0.abs
+ }
+}
+
+castable! {
+ FontSize,
+ Expected: "linear",
+ Value::Length(v) => Self(v.into()),
+ Value::Relative(v) => Self(v.into()),
+ Value::Linear(v) => Self(v),
+}
+
castable! {
Em,
Expected: "float",
@@ -353,6 +331,15 @@ castable! {
.collect(),
}
+impl Fold for Vec<(Tag, u32)> {
+ type Output = Self;
+
+ fn fold(mut self, outer: Self::Output) -> Self::Output {
+ self.extend(outer);
+ self
+ }
+}
+
/// A case transformation on text.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Case {
@@ -371,3 +358,62 @@ impl Case {
}
}
}
+
+/// A toggle that turns on and off alternatingly if folded.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub struct Toggle;
+
+impl Fold for Toggle {
+ type Output = bool;
+
+ fn fold(self, outer: Self::Output) -> Self::Output {
+ !outer
+ }
+}
+
+impl Fold for Decoration {
+ type Output = Vec<Self>;
+
+ fn fold(self, mut outer: Self::Output) -> Self::Output {
+ outer.insert(0, self);
+ outer
+ }
+}
+
+/// Strong text, rendered in boldface.
+#[derive(Debug, Hash)]
+pub struct StrongNode(pub Content);
+
+#[node(showable)]
+impl StrongNode {
+ fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
+ Ok(Content::show(Self(args.expect("body")?)))
+ }
+}
+
+impl Show for StrongNode {
+ fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
+ Ok(styles
+ .show::<Self, _>(ctx, [Value::Content(self.0.clone())])?
+ .unwrap_or_else(|| self.0.clone().styled(TextNode::STRONG, Toggle)))
+ }
+}
+
+/// Emphasized text, rendered with an italic face.
+#[derive(Debug, Hash)]
+pub struct EmphNode(pub Content);
+
+#[node(showable)]
+impl EmphNode {
+ fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
+ Ok(Content::show(Self(args.expect("body")?)))
+ }
+}
+
+impl Show for EmphNode {
+ fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
+ Ok(styles
+ .show::<Self, _>(ctx, [Value::Content(self.0.clone())])?
+ .unwrap_or_else(|| self.0.clone().styled(TextNode::EMPH, Toggle)))
+ }
+}
diff --git a/src/library/text/par.rs b/src/library/text/par.rs
index 97e5a3f5..67357135 100644
--- a/src/library/text/par.rs
+++ b/src/library/text/par.rs
@@ -28,6 +28,7 @@ pub enum ParChild {
#[node]
impl ParNode {
/// An ISO 639-1 language code.
+ #[property(referenced)]
pub const LANG: Option<EcoString> = None;
/// The direction for text and inline objects.
pub const DIR: Dir = Dir::LTR;
@@ -611,7 +612,7 @@ fn breakpoints<'a>(
let mut lang = None;
if styles.get(ParNode::HYPHENATE).unwrap_or(styles.get(ParNode::JUSTIFY)) {
lang = styles
- .get_ref(ParNode::LANG)
+ .get(ParNode::LANG)
.as_ref()
.and_then(|iso| iso.as_bytes().try_into().ok())
.and_then(hypher::Lang::from_iso);
@@ -768,7 +769,7 @@ fn stack(
regions: &Regions,
styles: StyleChain,
) -> Vec<Arc<Frame>> {
- let em = styles.get(TextNode::SIZE).abs;
+ let em = styles.get(TextNode::SIZE);
let leading = styles.get(ParNode::LEADING).resolve(em);
let align = styles.get(ParNode::ALIGN);
let justify = styles.get(ParNode::JUSTIFY);
@@ -832,7 +833,7 @@ fn commit(
if let Some(glyph) = text.glyphs.first() {
if text.styles.get(TextNode::OVERHANG) {
let start = text.dir.is_positive();
- let em = text.styles.get(TextNode::SIZE).abs;
+ let em = text.styles.get(TextNode::SIZE);
let amount = overhang(glyph.c, start) * glyph.x_advance.resolve(em);
offset -= amount;
remaining += amount;
@@ -847,7 +848,7 @@ fn commit(
&& (reordered.len() > 1 || text.glyphs.len() > 1)
{
let start = !text.dir.is_positive();
- let em = text.styles.get(TextNode::SIZE).abs;
+ let em = text.styles.get(TextNode::SIZE);
let amount = overhang(glyph.c, start) * glyph.x_advance.resolve(em);
remaining += amount;
}
diff --git a/src/library/text/raw.rs b/src/library/text/raw.rs
index 5c2133c2..f09bc1d0 100644
--- a/src/library/text/raw.rs
+++ b/src/library/text/raw.rs
@@ -3,7 +3,7 @@ use syntect::easy::HighlightLines;
use syntect::highlighting::{FontStyle, Highlighter, Style, Theme, ThemeSet};
use syntect::parsing::SyntaxSet;
-use super::{FontFamily, TextNode};
+use super::{FontFamily, TextNode, Toggle};
use crate::library::prelude::*;
use crate::source::SourceId;
use crate::syntax::{self, RedNode};
@@ -27,8 +27,10 @@ pub struct RawNode {
#[node(showable)]
impl RawNode {
/// The raw text's font family. Just the normal text family if `none`.
+ #[property(referenced)]
pub const FAMILY: Smart<FontFamily> = Smart::Custom(FontFamily::new("IBM Plex Mono"));
/// The language to syntax-highlight in.
+ #[property(referenced)]
pub const LANG: Option<EcoString> = None;
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
@@ -41,7 +43,7 @@ impl RawNode {
impl Show for RawNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
- let lang = styles.get_ref(Self::LANG).as_ref();
+ let lang = styles.get(Self::LANG).as_ref();
let foreground = THEME
.settings
.foreground
@@ -49,14 +51,16 @@ impl Show for RawNode {
.unwrap_or(Color::BLACK)
.into();
- let mut content = if let Some(content) = styles.show(self, ctx, [
+ let args = [
Value::Str(self.text.clone()),
match lang {
Some(lang) => Value::Str(lang.clone()),
None => Value::None,
},
Value::Bool(self.block),
- ])? {
+ ];
+
+ let mut content = if let Some(content) = styles.show::<Self, _>(ctx, args)? {
content
} else if matches!(
lang.map(|s| s.to_lowercase()).as_deref(),
@@ -93,8 +97,8 @@ impl Show for RawNode {
};
let mut map = StyleMap::new();
- if let Smart::Custom(family) = styles.get_cloned(Self::FAMILY) {
- map.set_family(family, styles);
+ if let Smart::Custom(family) = styles.get(Self::FAMILY) {
+ map.set_family(family.clone(), styles);
}
content = content.styled_with_map(map);
@@ -118,11 +122,11 @@ fn styled(piece: &str, foreground: Paint, style: Style) -> Content {
}
if style.font_style.contains(FontStyle::BOLD) {
- styles.set(TextNode::STRONG, true);
+ styles.set(TextNode::STRONG, Toggle);
}
if style.font_style.contains(FontStyle::ITALIC) {
- styles.set(TextNode::EMPH, true);
+ styles.set(TextNode::EMPH, Toggle);
}
if style.font_style.contains(FontStyle::UNDERLINE) {
diff --git a/src/library/text/shaping.rs b/src/library/text/shaping.rs
index 6087032f..66936792 100644
--- a/src/library/text/shaping.rs
+++ b/src/library/text/shaping.rs
@@ -77,7 +77,7 @@ impl<'a> ShapedText<'a> {
for (face_id, group) in self.glyphs.as_ref().group_by_key(|g| g.face_id) {
let pos = Point::new(offset, self.baseline);
- let size = self.styles.get(TextNode::SIZE).abs;
+ let size = self.styles.get(TextNode::SIZE);
let fill = self.styles.get(TextNode::FILL);
let glyphs = group
.iter()
@@ -99,7 +99,7 @@ impl<'a> ShapedText<'a> {
let width = text.width();
// Apply line decorations.
- for deco in self.styles.get_cloned(TextNode::LINES) {
+ for deco in self.styles.get(TextNode::DECO) {
decorate(&mut frame, &deco, fonts, &text, pos, width);
}
@@ -108,7 +108,7 @@ impl<'a> ShapedText<'a> {
}
// Apply link if it exists.
- if let Some(url) = self.styles.get_ref(TextNode::LINK) {
+ if let Some(url) = self.styles.get(TextNode::LINK) {
frame.link(url);
}
@@ -127,7 +127,7 @@ impl<'a> ShapedText<'a> {
.filter(|g| g.is_space())
.map(|g| g.x_advance)
.sum::<Em>()
- .resolve(self.styles.get(TextNode::SIZE).abs)
+ .resolve(self.styles.get(TextNode::SIZE))
}
/// Reshape a range of the shaped text, reusing information from this
@@ -154,7 +154,7 @@ impl<'a> ShapedText<'a> {
/// Push a hyphen to end of the text.
pub fn push_hyphen(&mut self, fonts: &mut FontStore) {
- let size = self.styles.get(TextNode::SIZE).abs;
+ let size = self.styles.get(TextNode::SIZE);
let variant = variant(self.styles);
families(self.styles).find_map(|family| {
let face_id = fonts.select(family, variant)?;
@@ -467,7 +467,7 @@ fn measure(
let mut top = Length::zero();
let mut bottom = Length::zero();
- let size = styles.get(TextNode::SIZE).abs;
+ let size = styles.get(TextNode::SIZE);
let top_edge = styles.get(TextNode::TOP_EDGE);
let bottom_edge = styles.get(TextNode::BOTTOM_EDGE);
@@ -537,7 +537,7 @@ fn families(styles: StyleChain) -> impl Iterator<Item = &str> + Clone {
let tail = if styles.get(TextNode::FALLBACK) { FALLBACKS } else { &[] };
styles
- .get_ref(TextNode::FAMILY)
+ .get(TextNode::FAMILY)
.iter()
.map(|family| family.as_str())
.chain(tail.iter().copied())
@@ -609,7 +609,7 @@ fn tags(styles: StyleChain) -> Vec<Feature> {
feat(b"frac", 1);
}
- for &(tag, value) in styles.get_ref(TextNode::FEATURES).iter() {
+ for (tag, value) in styles.get(TextNode::FEATURES) {
tags.push(Feature::new(tag, value, ..))
}