summaryrefslogtreecommitdiff
path: root/src/model
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-04-24 15:42:56 +0200
committerLaurenz <laurmaedje@gmail.com>2022-04-24 15:47:42 +0200
commit8fbb11fc05b3313bf102c1f23693290661d00863 (patch)
treea198db77338f1cc1304fe50f55020b08e22bca60 /src/model
parente4ee14e54fb87961096856c7ea105435b7cc3c45 (diff)
Extract `model` module
Diffstat (limited to 'src/model')
-rw-r--r--src/model/collapse.rs100
-rw-r--r--src/model/content.rs604
-rw-r--r--src/model/layout.rs383
-rw-r--r--src/model/mod.rs14
-rw-r--r--src/model/show.rs112
-rw-r--r--src/model/styles.rs831
6 files changed, 2044 insertions, 0 deletions
diff --git a/src/model/collapse.rs b/src/model/collapse.rs
new file mode 100644
index 00000000..5e7a25f8
--- /dev/null
+++ b/src/model/collapse.rs
@@ -0,0 +1,100 @@
+use super::{StyleChain, StyleVec, StyleVecBuilder};
+
+/// A wrapper around a [`StyleVecBuilder`] that allows to collapse items.
+pub struct CollapsingBuilder<'a, T> {
+ builder: StyleVecBuilder<'a, T>,
+ staged: Vec<(T, StyleChain<'a>, Option<u8>)>,
+ last: Last,
+}
+
+/// What the last non-ignorant item was.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+enum Last {
+ Weak,
+ Destructive,
+ Supportive,
+}
+
+impl<'a, T> CollapsingBuilder<'a, T> {
+ /// Create a new style-vec builder.
+ pub fn new() -> Self {
+ Self {
+ builder: StyleVecBuilder::new(),
+ staged: vec![],
+ last: Last::Destructive,
+ }
+ }
+
+ /// Can only exist when there is at least one supportive item to its left
+ /// and to its right, with no destructive items or weak items in between to
+ /// its left and no destructive items in between to its right. There may be
+ /// ignorant items in between in both directions.
+ pub fn weak(&mut self, item: T, strength: u8, styles: StyleChain<'a>) {
+ if self.last != Last::Destructive {
+ if self.last == Last::Weak {
+ if let Some(i) = self
+ .staged
+ .iter()
+ .position(|(.., prev)| prev.map_or(false, |p| p < strength))
+ {
+ self.staged.remove(i);
+ } else {
+ return;
+ }
+ }
+
+ self.staged.push((item, styles, Some(strength)));
+ self.last = Last::Weak;
+ }
+ }
+
+ /// Forces nearby weak items to collapse.
+ pub fn destructive(&mut self, item: T, styles: StyleChain<'a>) {
+ self.flush(false);
+ self.push(item, styles);
+ self.last = Last::Destructive;
+ }
+
+ /// Allows nearby weak items to exist.
+ pub fn supportive(&mut self, item: T, styles: StyleChain<'a>) {
+ self.flush(true);
+ self.push(item, styles);
+ self.last = Last::Supportive;
+ }
+
+ /// Has no influence on other items.
+ pub fn ignorant(&mut self, item: T, styles: StyleChain<'a>) {
+ self.staged.push((item, styles, None));
+ }
+
+ /// Iterate over the contained items.
+ pub fn items(&self) -> impl DoubleEndedIterator<Item = &T> {
+ self.builder.items().chain(self.staged.iter().map(|(item, ..)| item))
+ }
+
+ /// Return the finish style vec and the common prefix chain.
+ pub fn finish(mut self) -> (StyleVec<T>, StyleChain<'a>) {
+ self.flush(false);
+ self.builder.finish()
+ }
+
+ /// Push the staged items, filtering out weak items if `supportive` is false.
+ fn flush(&mut self, supportive: bool) {
+ for (item, styles, strength) in self.staged.drain(..) {
+ if supportive || strength.is_none() {
+ self.builder.push(item, styles);
+ }
+ }
+ }
+
+ /// Push a new item into the style vector.
+ fn push(&mut self, item: T, styles: StyleChain<'a>) {
+ self.builder.push(item, styles);
+ }
+}
+
+impl<'a, T> Default for CollapsingBuilder<'a, T> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
diff --git a/src/model/content.rs b/src/model/content.rs
new file mode 100644
index 00000000..2465f0e3
--- /dev/null
+++ b/src/model/content.rs
@@ -0,0 +1,604 @@
+use std::fmt::Debug;
+use std::hash::Hash;
+use std::iter::Sum;
+use std::ops::{Add, AddAssign};
+
+use typed_arena::Arena;
+
+use super::{
+ CollapsingBuilder, Interruption, Key, Layout, LayoutNode, Show, ShowNode, StyleMap,
+ StyleVecBuilder,
+};
+use crate::diag::StrResult;
+use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode, Spacing};
+use crate::library::prelude::*;
+use crate::library::structure::{ListItem, ListKind, ListNode, ORDERED, UNORDERED};
+use crate::library::text::{DecoNode, ParChild, ParNode, UNDERLINE};
+use crate::util::EcoString;
+
+/// Composable representation of styled content.
+///
+/// This results from:
+/// - anything written between square brackets in Typst
+/// - any node constructor
+///
+/// Content is represented as a tree of nodes. There are two nodes of special
+/// interest:
+///
+/// 1. A `Styled` node attaches a style map to other content. For example, a
+/// single bold word could be represented as a `Styled(Text("Hello"),
+/// [TextNode::STRONG: true])` node.
+///
+/// 2. A `Sequence` node content combines other arbitrary content and is the
+/// representation of a "flow" of other nodes. So, when you write `[Hi] +
+/// [you]` in Typst, this type's [`Add`] implementation is invoked and the
+/// two [`Text`](Self::Text) nodes are combined into a single
+/// [`Sequence`](Self::Sequence) node. A sequence may contain nested
+/// sequences.
+#[derive(PartialEq, Clone, Hash)]
+pub enum Content {
+ /// A word space.
+ Space,
+ /// A forced line break. If soft (`true`), the preceding line can still be
+ /// justified, if hard (`false`) not.
+ Linebreak(bool),
+ /// Horizontal spacing.
+ Horizontal(Spacing),
+ /// Plain text.
+ Text(EcoString),
+ /// A smart quote, may be single (`false`) or double (`true`).
+ Quote(bool),
+ /// An inline-level node.
+ Inline(LayoutNode),
+ /// A paragraph break.
+ Parbreak,
+ /// A column break.
+ Colbreak,
+ /// Vertical spacing.
+ Vertical(Spacing),
+ /// A block-level node.
+ Block(LayoutNode),
+ /// An item in an unordered list.
+ List(ListItem),
+ /// An item in an ordered list.
+ Enum(ListItem),
+ /// A page break.
+ Pagebreak,
+ /// A page node.
+ Page(PageNode),
+ /// A node that can be realized with styles.
+ Show(ShowNode),
+ /// Content with attached styles.
+ Styled(Arc<(Self, StyleMap)>),
+ /// A sequence of multiple nodes.
+ Sequence(Arc<Vec<Self>>),
+}
+
+impl Content {
+ /// Create empty content.
+ pub fn new() -> Self {
+ Self::sequence(vec![])
+ }
+
+ /// Create content from an inline-level node.
+ pub fn inline<T>(node: T) -> Self
+ where
+ T: Layout + Debug + Hash + Sync + Send + 'static,
+ {
+ Self::Inline(node.pack())
+ }
+
+ /// Create content from a block-level node.
+ pub fn block<T>(node: T) -> Self
+ where
+ T: Layout + Debug + Hash + Sync + Send + 'static,
+ {
+ Self::Block(node.pack())
+ }
+
+ /// Create content from a showable node.
+ pub fn show<T>(node: T) -> Self
+ where
+ T: Show + Debug + Hash + Sync + Send + 'static,
+ {
+ Self::Show(node.pack())
+ }
+
+ /// Create a new sequence nodes from multiples nodes.
+ pub fn sequence(seq: Vec<Self>) -> Self {
+ if seq.len() == 1 {
+ seq.into_iter().next().unwrap()
+ } else {
+ Self::Sequence(Arc::new(seq))
+ }
+ }
+
+ /// Repeat this content `n` times.
+ pub fn repeat(&self, n: i64) -> StrResult<Self> {
+ let count = usize::try_from(n)
+ .map_err(|_| format!("cannot repeat this content {} times", n))?;
+
+ Ok(Self::sequence(vec![self.clone(); count]))
+ }
+
+ /// Style this content with a single style property.
+ 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) {
+ map.apply(key, value);
+ return self;
+ }
+ }
+
+ Self::Styled(Arc::new((self, StyleMap::with(key, value))))
+ }
+
+ /// Style this content with a full style map.
+ pub fn styled_with_map(mut self, styles: StyleMap) -> Self {
+ if styles.is_empty() {
+ return self;
+ }
+
+ if let Self::Styled(styled) = &mut self {
+ if let Some((_, map)) = Arc::get_mut(styled) {
+ map.apply_map(&styles);
+ return self;
+ }
+ }
+
+ Self::Styled(Arc::new((self, styles)))
+ }
+
+ /// Underline this content.
+ pub fn underlined(self) -> Self {
+ Self::show(DecoNode::<UNDERLINE>(self))
+ }
+
+ /// Return a node that is spaced apart at top and bottom.
+ pub fn spaced(self, above: Length, below: Length) -> Self {
+ if above.is_zero() && below.is_zero() {
+ return self;
+ }
+
+ let mut seq = vec![];
+ if !above.is_zero() {
+ seq.push(Content::Vertical(above.into()));
+ }
+
+ seq.push(self);
+
+ if !below.is_zero() {
+ seq.push(Content::Vertical(below.into()));
+ }
+
+ Self::sequence(seq)
+ }
+
+ /// Layout this content into a collection of pages.
+ pub fn layout(&self, ctx: &mut Context) -> TypResult<Vec<Arc<Frame>>> {
+ let sya = Arena::new();
+ let tpa = Arena::new();
+
+ let styles = ctx.styles.clone();
+ let styles = StyleChain::with_root(&styles);
+
+ let mut builder = Builder::new(&sya, &tpa, true);
+ builder.process(ctx, self, styles)?;
+ builder.finish(ctx, styles)?;
+
+ let mut frames = vec![];
+ let (pages, shared) = builder.pages.unwrap().finish();
+
+ for (page, map) in pages.iter() {
+ let number = 1 + frames.len();
+ frames.extend(page.layout(ctx, number, map.chain(&shared))?);
+ }
+
+ Ok(frames)
+ }
+}
+
+impl Layout for Content {
+ fn layout(
+ &self,
+ ctx: &mut Context,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> TypResult<Vec<Arc<Frame>>> {
+ let sya = Arena::new();
+ let tpa = Arena::new();
+
+ let mut builder = Builder::new(&sya, &tpa, false);
+ builder.process(ctx, self, styles)?;
+ builder.finish(ctx, styles)?;
+
+ let (flow, shared) = builder.flow.finish();
+ FlowNode(flow).layout(ctx, regions, shared)
+ }
+
+ fn pack(self) -> LayoutNode {
+ match self {
+ Content::Block(node) => node,
+ other => LayoutNode::new(other),
+ }
+ }
+}
+
+impl Default for Content {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Debug for Content {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ match self {
+ Self::Space => f.pad("Space"),
+ Self::Linebreak(soft) => write!(f, "Linebreak({soft})"),
+ Self::Horizontal(kind) => write!(f, "Horizontal({kind:?})"),
+ Self::Text(text) => write!(f, "Text({text:?})"),
+ Self::Quote(double) => write!(f, "Quote({double})"),
+ Self::Inline(node) => {
+ f.write_str("Inline(")?;
+ node.fmt(f)?;
+ f.write_str(")")
+ }
+ Self::Parbreak => f.pad("Parbreak"),
+ Self::Colbreak => f.pad("Colbreak"),
+ Self::Vertical(kind) => write!(f, "Vertical({kind:?})"),
+ Self::Block(node) => {
+ f.write_str("Block(")?;
+ node.fmt(f)?;
+ f.write_str(")")
+ }
+ Self::List(item) => {
+ f.write_str("- ")?;
+ item.body.fmt(f)
+ }
+ Self::Enum(item) => {
+ if let Some(number) = item.number {
+ write!(f, "{}", number)?;
+ }
+ f.write_str(". ")?;
+ item.body.fmt(f)
+ }
+ Self::Pagebreak => f.pad("Pagebreak"),
+ Self::Page(page) => page.fmt(f),
+ Self::Show(node) => {
+ f.write_str("Show(")?;
+ node.fmt(f)?;
+ f.write_str(")")
+ }
+ Self::Styled(styled) => {
+ let (sub, map) = styled.as_ref();
+ map.fmt(f)?;
+ sub.fmt(f)
+ }
+ Self::Sequence(seq) => f.debug_list().entries(seq.iter()).finish(),
+ }
+ }
+}
+
+impl Add for Content {
+ type Output = Self;
+
+ fn add(self, rhs: Self) -> Self::Output {
+ Self::Sequence(match (self, rhs) {
+ (Self::Sequence(mut lhs), Self::Sequence(rhs)) => {
+ let mutable = Arc::make_mut(&mut lhs);
+ match Arc::try_unwrap(rhs) {
+ Ok(vec) => mutable.extend(vec),
+ Err(rc) => mutable.extend(rc.iter().cloned()),
+ }
+ lhs
+ }
+ (Self::Sequence(mut lhs), rhs) => {
+ Arc::make_mut(&mut lhs).push(rhs);
+ lhs
+ }
+ (lhs, Self::Sequence(mut rhs)) => {
+ Arc::make_mut(&mut rhs).insert(0, lhs);
+ rhs
+ }
+ (lhs, rhs) => Arc::new(vec![lhs, rhs]),
+ })
+ }
+}
+
+impl AddAssign for Content {
+ fn add_assign(&mut self, rhs: Self) {
+ *self = std::mem::take(self) + rhs;
+ }
+}
+
+impl Sum for Content {
+ fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
+ Self::sequence(iter.collect())
+ }
+}
+
+/// Builds a flow or page nodes from content.
+struct Builder<'a> {
+ /// An arena where intermediate style chains are stored.
+ sya: &'a Arena<StyleChain<'a>>,
+ /// An arena where intermediate content resulting from show rules is stored.
+ tpa: &'a Arena<Content>,
+ /// The already built page runs.
+ pages: Option<StyleVecBuilder<'a, PageNode>>,
+ /// The currently built list.
+ list: Option<ListBuilder<'a>>,
+ /// The currently built flow.
+ flow: CollapsingBuilder<'a, FlowChild>,
+ /// The currently built paragraph.
+ par: CollapsingBuilder<'a, ParChild>,
+ /// Whether to keep the next page even if it is empty.
+ keep_next: bool,
+}
+
+/// Builds an unordered or ordered list from items.
+struct ListBuilder<'a> {
+ styles: StyleChain<'a>,
+ kind: ListKind,
+ items: Vec<ListItem>,
+ tight: bool,
+ staged: Vec<(&'a Content, StyleChain<'a>)>,
+}
+
+impl<'a> Builder<'a> {
+ /// Prepare the builder.
+ fn new(sya: &'a Arena<StyleChain<'a>>, tpa: &'a Arena<Content>, top: bool) -> Self {
+ Self {
+ sya,
+ tpa,
+ pages: top.then(|| StyleVecBuilder::new()),
+ flow: CollapsingBuilder::new(),
+ list: None,
+ par: CollapsingBuilder::new(),
+ keep_next: true,
+ }
+ }
+
+ /// Process content.
+ fn process(
+ &mut self,
+ ctx: &mut Context,
+ content: &'a Content,
+ styles: StyleChain<'a>,
+ ) -> TypResult<()> {
+ if let Some(builder) = &mut self.list {
+ match content {
+ Content::Space => {
+ builder.staged.push((content, styles));
+ return Ok(());
+ }
+ Content::Parbreak => {
+ builder.staged.push((content, styles));
+ return Ok(());
+ }
+ Content::List(item) if builder.kind == UNORDERED => {
+ builder.tight &=
+ builder.staged.iter().all(|&(t, _)| *t != Content::Parbreak);
+ builder.staged.clear();
+ builder.items.push(item.clone());
+ return Ok(());
+ }
+ Content::Enum(item) if builder.kind == ORDERED => {
+ builder.tight &=
+ builder.staged.iter().all(|&(t, _)| *t != Content::Parbreak);
+ builder.staged.clear();
+ builder.items.push(item.clone());
+ return Ok(());
+ }
+ _ => self.finish_list(ctx)?,
+ }
+ }
+
+ match content {
+ Content::Space => {
+ self.par.weak(ParChild::Text(' '.into()), 0, styles);
+ }
+ Content::Linebreak(soft) => {
+ let c = if *soft { '\u{2028}' } else { '\n' };
+ self.par.destructive(ParChild::Text(c.into()), styles);
+ }
+ Content::Horizontal(kind) => {
+ let child = ParChild::Spacing(*kind);
+ if kind.is_fractional() {
+ self.par.destructive(child, styles);
+ } else {
+ self.par.ignorant(child, styles);
+ }
+ }
+ Content::Quote(double) => {
+ self.par.supportive(ParChild::Quote(*double), styles);
+ }
+ Content::Text(text) => {
+ self.par.supportive(ParChild::Text(text.clone()), styles);
+ }
+ Content::Inline(node) => {
+ self.par.supportive(ParChild::Node(node.clone()), styles);
+ }
+ Content::Parbreak => {
+ self.finish_par(styles);
+ self.flow.weak(FlowChild::Parbreak, 1, styles);
+ }
+ Content::Colbreak => {
+ self.finish_par(styles);
+ self.flow.destructive(FlowChild::Colbreak, styles);
+ }
+ Content::Vertical(kind) => {
+ self.finish_par(styles);
+ let child = FlowChild::Spacing(*kind);
+ if kind.is_fractional() {
+ self.flow.destructive(child, styles);
+ } else {
+ self.flow.ignorant(child, styles);
+ }
+ }
+ Content::Block(node) => {
+ self.finish_par(styles);
+ let child = FlowChild::Node(node.clone());
+ if node.is::<PlaceNode>() {
+ self.flow.ignorant(child, styles);
+ } else {
+ self.flow.supportive(child, styles);
+ }
+ self.finish_par(styles);
+ }
+ Content::List(item) => {
+ self.list = Some(ListBuilder {
+ styles,
+ kind: UNORDERED,
+ items: vec![item.clone()],
+ tight: true,
+ staged: vec![],
+ });
+ }
+ Content::Enum(item) => {
+ self.list = Some(ListBuilder {
+ styles,
+ kind: ORDERED,
+ items: vec![item.clone()],
+ tight: true,
+ staged: vec![],
+ });
+ }
+ Content::Pagebreak => {
+ self.finish_page(ctx, true, true, styles)?;
+ }
+ Content::Page(page) => {
+ self.finish_page(ctx, false, false, styles)?;
+ if let Some(pages) = &mut self.pages {
+ pages.push(page.clone(), styles);
+ }
+ }
+ Content::Show(node) => {
+ let id = node.id();
+ let realized = match styles.realize(ctx, node)? {
+ Some(content) => content,
+ None => node.realize(ctx, styles)?,
+ };
+ let content = node.finalize(ctx, styles, realized)?;
+ let stored = self.tpa.alloc(content);
+ self.process(ctx, stored, styles.unscoped(id))?;
+ }
+ Content::Styled(styled) => {
+ let (sub, map) = styled.as_ref();
+ let stored = self.sya.alloc(styles);
+ let styles = map.chain(stored);
+
+ let interruption = map.interruption();
+ match interruption {
+ Some(Interruption::Page) => {
+ self.finish_page(ctx, false, true, styles)?
+ }
+ Some(Interruption::Par) => self.finish_par(styles),
+ None => {}
+ }
+
+ self.process(ctx, sub, styles)?;
+
+ match interruption {
+ Some(Interruption::Page) => {
+ self.finish_page(ctx, true, false, styles)?
+ }
+ Some(Interruption::Par) => self.finish_par(styles),
+ None => {}
+ }
+ }
+ Content::Sequence(seq) => {
+ for sub in seq.iter() {
+ self.process(ctx, sub, styles)?;
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Finish the currently built paragraph.
+ fn finish_par(&mut self, styles: StyleChain<'a>) {
+ let (mut par, shared) = std::mem::take(&mut self.par).finish();
+ if !par.is_empty() {
+ // Paragraph indent should only apply if the paragraph starts with
+ // text and follows directly after another paragraph.
+ let indent = shared.get(ParNode::INDENT);
+ if !indent.is_zero()
+ && par
+ .items()
+ .find_map(|child| match child {
+ ParChild::Spacing(_) => None,
+ ParChild::Text(_) | ParChild::Quote(_) => Some(true),
+ ParChild::Node(_) => Some(false),
+ })
+ .unwrap_or_default()
+ && self
+ .flow
+ .items()
+ .rev()
+ .find_map(|child| match child {
+ FlowChild::Leading => None,
+ FlowChild::Parbreak => None,
+ FlowChild::Node(node) => Some(node.is::<ParNode>()),
+ FlowChild::Spacing(_) => Some(false),
+ FlowChild::Colbreak => Some(false),
+ })
+ .unwrap_or_default()
+ {
+ par.push_front(ParChild::Spacing(indent.into()));
+ }
+
+ let node = ParNode(par).pack();
+ self.flow.supportive(FlowChild::Node(node), shared);
+ }
+ self.flow.weak(FlowChild::Leading, 0, styles);
+ }
+
+ /// Finish the currently built list.
+ fn finish_list(&mut self, ctx: &mut Context) -> TypResult<()> {
+ let ListBuilder { styles, kind, items, tight, staged } = match self.list.take() {
+ Some(list) => list,
+ None => return Ok(()),
+ };
+
+ let content = match kind {
+ UNORDERED => Content::show(ListNode::<UNORDERED> { start: 1, tight, items }),
+ ORDERED | _ => Content::show(ListNode::<ORDERED> { start: 1, tight, items }),
+ };
+
+ let stored = self.tpa.alloc(content);
+ self.process(ctx, stored, styles)?;
+ for (content, styles) in staged {
+ self.process(ctx, content, styles)?;
+ }
+
+ Ok(())
+ }
+
+ /// Finish the currently built page run.
+ fn finish_page(
+ &mut self,
+ ctx: &mut Context,
+ keep_last: bool,
+ keep_next: bool,
+ styles: StyleChain<'a>,
+ ) -> TypResult<()> {
+ self.finish_list(ctx)?;
+ self.finish_par(styles);
+ if let Some(pages) = &mut self.pages {
+ let (flow, shared) = std::mem::take(&mut self.flow).finish();
+ if !flow.is_empty() || (keep_last && self.keep_next) {
+ let styles = if flow.is_empty() { styles } else { shared };
+ let node = PageNode(FlowNode(flow).pack());
+ pages.push(node, styles);
+ }
+ }
+ self.keep_next = keep_next;
+ Ok(())
+ }
+
+ /// Finish everything.
+ fn finish(&mut self, ctx: &mut Context, styles: StyleChain<'a>) -> TypResult<()> {
+ self.finish_page(ctx, true, false, styles)
+ }
+}
diff --git a/src/model/layout.rs b/src/model/layout.rs
new file mode 100644
index 00000000..da6290ae
--- /dev/null
+++ b/src/model/layout.rs
@@ -0,0 +1,383 @@
+//! Layouting infrastructure.
+
+use std::any::Any;
+use std::fmt::{self, Debug, Formatter};
+use std::hash::Hash;
+use std::sync::Arc;
+
+use super::{Barrier, Resolve, StyleChain};
+use crate::diag::TypResult;
+use crate::eval::{RawAlign, RawLength};
+use crate::frame::{Element, Frame, Geometry};
+use crate::geom::{Align, Length, Paint, Point, Relative, Sides, Size, Spec, Stroke};
+use crate::library::graphics::MoveNode;
+use crate::library::layout::{AlignNode, PadNode};
+use crate::util::Prehashed;
+use crate::Context;
+
+/// A node that can be layouted into a sequence of regions.
+///
+/// Layout return one frame per used region alongside constraints that define
+/// whether the result is reusable in other regions.
+pub trait Layout: 'static {
+ /// Layout this node into the given regions, producing constrained frames.
+ fn layout(
+ &self,
+ ctx: &mut Context,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> TypResult<Vec<Arc<Frame>>>;
+
+ /// Convert to a packed node.
+ fn pack(self) -> LayoutNode
+ where
+ Self: Debug + Hash + Sized + Sync + Send + 'static,
+ {
+ LayoutNode::new(self)
+ }
+}
+
+/// A sequence of regions to layout into.
+#[derive(Debug, Clone, Hash)]
+pub struct Regions {
+ /// The (remaining) size of the first region.
+ pub first: Size,
+ /// The base size for relative sizing.
+ pub base: Size,
+ /// The height of followup regions. The width is the same for all regions.
+ pub backlog: Vec<Length>,
+ /// The height of the final region that is repeated once the backlog is
+ /// drained. The width is the same for all regions.
+ pub last: Option<Length>,
+ /// Whether nodes should expand to fill the regions instead of shrinking to
+ /// fit the content.
+ pub expand: Spec<bool>,
+}
+
+impl Regions {
+ /// Create a new region sequence with exactly one region.
+ pub fn one(size: Size, base: Size, expand: Spec<bool>) -> Self {
+ Self {
+ first: size,
+ base,
+ backlog: vec![],
+ last: None,
+ expand,
+ }
+ }
+
+ /// Create a new sequence of same-size regions that repeats indefinitely.
+ pub fn repeat(size: Size, base: Size, expand: Spec<bool>) -> Self {
+ Self {
+ first: size,
+ base,
+ backlog: vec![],
+ last: Some(size.y),
+ expand,
+ }
+ }
+
+ /// Create new regions where all sizes are mapped with `f`.
+ ///
+ /// Note that since all regions must have the same width, the width returned
+ /// by `f` is ignored for the backlog and the final region.
+ pub fn map<F>(&self, mut f: F) -> Self
+ where
+ F: FnMut(Size) -> Size,
+ {
+ let x = self.first.x;
+ Self {
+ first: f(self.first),
+ base: f(self.base),
+ backlog: self.backlog.iter().map(|&y| f(Size::new(x, y)).y).collect(),
+ last: self.last.map(|y| f(Size::new(x, y)).y),
+ expand: self.expand,
+ }
+ }
+
+ /// Whether the first region is full and a region break is called for.
+ pub fn is_full(&self) -> bool {
+ Length::zero().fits(self.first.y) && !self.in_last()
+ }
+
+ /// Whether the first region is the last usable region.
+ ///
+ /// If this is true, calling `next()` will have no effect.
+ pub fn in_last(&self) -> bool {
+ self.backlog.len() == 0 && self.last.map_or(true, |height| self.first.y == height)
+ }
+
+ /// Advance to the next region if there is any.
+ pub fn next(&mut self) {
+ if let Some(height) = (!self.backlog.is_empty())
+ .then(|| self.backlog.remove(0))
+ .or(self.last)
+ {
+ self.first.y = height;
+ self.base.y = height;
+ }
+ }
+
+ /// An iterator that returns the sizes of the first and all following
+ /// regions, equivalently to what would be produced by calling
+ /// [`next()`](Self::next) repeatedly until all regions are exhausted.
+ /// This iterater may be infinite.
+ pub fn iter(&self) -> impl Iterator<Item = Size> + '_ {
+ let first = std::iter::once(self.first);
+ let backlog = self.backlog.iter();
+ let last = self.last.iter().cycle();
+ first.chain(backlog.chain(last).map(|&h| Size::new(self.first.x, h)))
+ }
+}
+
+/// A type-erased layouting node with a precomputed hash.
+#[derive(Clone, Hash)]
+pub struct LayoutNode(Arc<Prehashed<dyn Bounds>>);
+
+impl LayoutNode {
+ /// Pack any layoutable node.
+ pub fn new<T>(node: T) -> Self
+ where
+ T: Layout + Debug + Hash + Sync + Send + 'static,
+ {
+ Self(Arc::new(Prehashed::new(node)))
+ }
+
+ /// Check whether the contained node is a specific layout node.
+ pub fn is<T: 'static>(&self) -> bool {
+ (**self.0).as_any().is::<T>()
+ }
+
+ /// A barrier for the node.
+ pub fn barrier(&self) -> Barrier {
+ (**self.0).barrier()
+ }
+
+ /// Try to downcast to a specific layout node.
+ pub fn downcast<T>(&self) -> Option<&T>
+ where
+ T: Layout + Debug + Hash + 'static,
+ {
+ (**self.0).as_any().downcast_ref()
+ }
+
+ /// Force a size for this node.
+ pub fn sized(self, sizing: Spec<Option<Relative<RawLength>>>) -> Self {
+ if sizing.any(Option::is_some) {
+ SizedNode { sizing, child: self }.pack()
+ } else {
+ self
+ }
+ }
+
+ /// Fill the frames resulting from a node.
+ pub fn filled(self, fill: Paint) -> Self {
+ FillNode { fill, child: self }.pack()
+ }
+
+ /// Stroke the frames resulting from a node.
+ pub fn stroked(self, stroke: Stroke) -> Self {
+ StrokeNode { stroke, child: self }.pack()
+ }
+
+ /// Set alignments for this node.
+ pub fn aligned(self, aligns: Spec<Option<RawAlign>>) -> Self {
+ if aligns.any(Option::is_some) {
+ AlignNode { aligns, child: self }.pack()
+ } else {
+ self
+ }
+ }
+
+ /// Pad this node at the sides.
+ pub fn padded(self, padding: Sides<Relative<RawLength>>) -> Self {
+ if !padding.left.is_zero()
+ || !padding.top.is_zero()
+ || !padding.right.is_zero()
+ || !padding.bottom.is_zero()
+ {
+ PadNode { padding, child: self }.pack()
+ } else {
+ self
+ }
+ }
+
+ /// Transform this node's contents without affecting layout.
+ pub fn moved(self, delta: Spec<Relative<RawLength>>) -> Self {
+ if delta.any(|r| !r.is_zero()) {
+ MoveNode { delta, child: self }.pack()
+ } else {
+ self
+ }
+ }
+}
+
+impl Layout for LayoutNode {
+ fn layout(
+ &self,
+ ctx: &mut Context,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> TypResult<Vec<Arc<Frame>>> {
+ ctx.query((self, regions, styles), |ctx, (node, regions, styles)| {
+ node.0.layout(ctx, regions, node.barrier().chain(&styles))
+ })
+ .clone()
+ }
+
+ fn pack(self) -> LayoutNode {
+ self
+ }
+}
+
+impl Default for LayoutNode {
+ fn default() -> Self {
+ EmptyNode.pack()
+ }
+}
+
+impl Debug for LayoutNode {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl PartialEq for LayoutNode {
+ fn eq(&self, other: &Self) -> bool {
+ self.0.eq(&other.0)
+ }
+}
+
+trait Bounds: Layout + Debug + Sync + Send + 'static {
+ fn as_any(&self) -> &dyn Any;
+ fn barrier(&self) -> Barrier;
+}
+
+impl<T> Bounds for T
+where
+ T: Layout + Debug + Hash + Sync + Send + 'static,
+{
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+
+ fn barrier(&self) -> Barrier {
+ Barrier::new::<T>()
+ }
+}
+
+/// A layout node that produces an empty frame.
+///
+/// The packed version of this is returned by [`PackedNode::default`].
+#[derive(Debug, Hash)]
+struct EmptyNode;
+
+impl Layout for EmptyNode {
+ fn layout(
+ &self,
+ _: &mut Context,
+ regions: &Regions,
+ _: StyleChain,
+ ) -> TypResult<Vec<Arc<Frame>>> {
+ Ok(vec![Arc::new(Frame::new(
+ regions.expand.select(regions.first, Size::zero()),
+ ))])
+ }
+}
+
+/// Fix the size of a node.
+#[derive(Debug, Hash)]
+struct SizedNode {
+ /// How to size the node horizontally and vertically.
+ sizing: Spec<Option<Relative<RawLength>>>,
+ /// The node to be sized.
+ child: LayoutNode,
+}
+
+impl Layout for SizedNode {
+ fn layout(
+ &self,
+ ctx: &mut Context,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> TypResult<Vec<Arc<Frame>>> {
+ // The "pod" is the region into which the child will be layouted.
+ let pod = {
+ // Resolve the sizing to a concrete size.
+ let size = self
+ .sizing
+ .resolve(styles)
+ .zip(regions.base)
+ .map(|(s, b)| s.map(|v| v.relative_to(b)))
+ .unwrap_or(regions.first);
+
+ // Select the appropriate base and expansion for the child depending
+ // on whether it is automatically or relatively sized.
+ let is_auto = self.sizing.map_is_none();
+ let base = is_auto.select(regions.base, size);
+ let expand = regions.expand | !is_auto;
+
+ Regions::one(size, base, expand)
+ };
+
+ // Layout the child.
+ let mut frames = self.child.layout(ctx, &pod, styles)?;
+
+ // Ensure frame size matches regions size if expansion is on.
+ let frame = &mut frames[0];
+ let target = regions.expand.select(regions.first, frame.size);
+ Arc::make_mut(frame).resize(target, Align::LEFT_TOP);
+
+ Ok(frames)
+ }
+}
+
+/// Fill the frames resulting from a node.
+#[derive(Debug, Hash)]
+struct FillNode {
+ /// How to fill the frames resulting from the `child`.
+ fill: Paint,
+ /// The node to fill.
+ child: LayoutNode,
+}
+
+impl Layout for FillNode {
+ fn layout(
+ &self,
+ ctx: &mut Context,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> TypResult<Vec<Arc<Frame>>> {
+ let mut frames = self.child.layout(ctx, regions, styles)?;
+ for frame in &mut frames {
+ let shape = Geometry::Rect(frame.size).filled(self.fill);
+ Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
+ }
+ Ok(frames)
+ }
+}
+
+/// Stroke the frames resulting from a node.
+#[derive(Debug, Hash)]
+struct StrokeNode {
+ /// How to stroke the frames resulting from the `child`.
+ stroke: Stroke,
+ /// The node to stroke.
+ child: LayoutNode,
+}
+
+impl Layout for StrokeNode {
+ fn layout(
+ &self,
+ ctx: &mut Context,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> TypResult<Vec<Arc<Frame>>> {
+ let mut frames = self.child.layout(ctx, regions, styles)?;
+ for frame in &mut frames {
+ let shape = Geometry::Rect(frame.size).stroked(self.stroke);
+ Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
+ }
+ Ok(frames)
+ }
+}
diff --git a/src/model/mod.rs b/src/model/mod.rs
new file mode 100644
index 00000000..df39207f
--- /dev/null
+++ b/src/model/mod.rs
@@ -0,0 +1,14 @@
+//! Structured representation of styled content.
+
+#[macro_use]
+mod styles;
+mod collapse;
+mod content;
+mod layout;
+mod show;
+
+pub use collapse::*;
+pub use content::*;
+pub use layout::*;
+pub use show::*;
+pub use styles::*;
diff --git a/src/model/show.rs b/src/model/show.rs
new file mode 100644
index 00000000..5f76ba19
--- /dev/null
+++ b/src/model/show.rs
@@ -0,0 +1,112 @@
+use std::any::{Any, TypeId};
+use std::fmt::{self, Debug, Formatter};
+use std::hash::Hash;
+use std::sync::Arc;
+
+use super::{Content, StyleChain};
+use crate::diag::TypResult;
+use crate::eval::Dict;
+use crate::util::Prehashed;
+use crate::Context;
+
+/// A node that can be realized given some styles.
+pub trait Show: 'static {
+ /// Encode this node into a dictionary.
+ fn encode(&self) -> Dict;
+
+ /// The base recipe for this node that is executed if there is no
+ /// user-defined show rule.
+ fn realize(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content>;
+
+ /// Finalize this node given the realization of a base or user recipe. Use
+ /// this for effects that should work even in the face of a user-defined
+ /// show rule, for example:
+ /// - Application of general settable properties
+ /// - Attaching things like semantics to a heading
+ ///
+ /// Defaults to just the realized content.
+ #[allow(unused_variables)]
+ fn finalize(
+ &self,
+ ctx: &mut Context,
+ styles: StyleChain,
+ realized: Content,
+ ) -> TypResult<Content> {
+ Ok(realized)
+ }
+
+ /// Convert to a packed show node.
+ fn pack(self) -> ShowNode
+ where
+ Self: Debug + Hash + Sized + Sync + Send + 'static,
+ {
+ ShowNode::new(self)
+ }
+}
+
+/// A type-erased showable node with a precomputed hash.
+#[derive(Clone, Hash)]
+pub struct ShowNode(Arc<Prehashed<dyn Bounds>>);
+
+impl ShowNode {
+ /// Pack any showable node.
+ pub fn new<T>(node: T) -> Self
+ where
+ T: Show + Debug + Hash + Sync + Send + 'static,
+ {
+ Self(Arc::new(Prehashed::new(node)))
+ }
+
+ /// The type id of this node.
+ pub fn id(&self) -> TypeId {
+ self.0.as_any().type_id()
+ }
+}
+
+impl Show for ShowNode {
+ fn encode(&self) -> Dict {
+ self.0.encode()
+ }
+
+ fn realize(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
+ self.0.realize(ctx, styles)
+ }
+
+ fn finalize(
+ &self,
+ ctx: &mut Context,
+ styles: StyleChain,
+ realized: Content,
+ ) -> TypResult<Content> {
+ self.0.finalize(ctx, styles, realized)
+ }
+
+ fn pack(self) -> ShowNode {
+ self
+ }
+}
+
+impl Debug for ShowNode {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl PartialEq for ShowNode {
+ fn eq(&self, other: &Self) -> bool {
+ self.0.eq(&other.0)
+ }
+}
+
+trait Bounds: Show + Debug + Sync + Send + 'static {
+ fn as_any(&self) -> &dyn Any;
+}
+
+impl<T> Bounds for T
+where
+ T: Show + Debug + Hash + Sync + Send + 'static,
+{
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+}
diff --git a/src/model/styles.rs b/src/model/styles.rs
new file mode 100644
index 00000000..2195568d
--- /dev/null
+++ b/src/model/styles.rs
@@ -0,0 +1,831 @@
+use std::any::{Any, TypeId};
+use std::fmt::{self, Debug, Formatter};
+use std::hash::Hash;
+use std::marker::PhantomData;
+use std::sync::Arc;
+
+use super::{Content, Layout, Show, ShowNode};
+use crate::diag::{At, TypResult};
+use crate::eval::{Args, Func, Node, Smart, Value};
+use crate::geom::{Numeric, Relative, Sides, Spec};
+use crate::library::layout::PageNode;
+use crate::library::text::{FontFamily, ParNode, TextNode};
+use crate::syntax::Span;
+use crate::util::Prehashed;
+use crate::Context;
+
+/// A map of style properties.
+#[derive(Default, Clone, PartialEq, Hash)]
+pub struct StyleMap(Vec<Entry>);
+
+impl StyleMap {
+ /// Create a new, empty style map.
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Whether this map contains no styles.
+ pub fn is_empty(&self) -> bool {
+ self.0.is_empty()
+ }
+
+ /// Create a style map from a single property-value pair.
+ pub fn with<'a, K: Key<'a>>(key: K, value: K::Value) -> Self {
+ let mut styles = Self::new();
+ styles.set(key, value);
+ styles
+ }
+
+ /// 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 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);
+ }
+ }
+
+ /// 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(
+ TextNode::FAMILY,
+ std::iter::once(preferred)
+ .chain(existing.get(TextNode::FAMILY).iter().cloned())
+ .collect(),
+ );
+ }
+
+ /// 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)));
+ }
+
+ /// Whether the map contains a style property for the given key.
+ pub fn contains<'a, K: Key<'a>>(&self, _: K) -> bool {
+ self.0
+ .iter()
+ .filter_map(|entry| entry.property())
+ .any(|property| property.key == TypeId::of::<K>())
+ }
+
+ /// Make `self` the first link of the `tail` chain.
+ ///
+ /// The resulting style chain contains styles from `self` as well as
+ /// `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() {
+ *tail
+ } else {
+ StyleChain { head: &self.0, tail: Some(tail) }
+ }
+ }
+
+ /// 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 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 kind of of structure the map interrupts.
+ pub fn interruption(&self) -> Option<Interruption> {
+ 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.0.iter().rev() {
+ writeln!(f, "{:?}", entry)?;
+ }
+ Ok(())
+ }
+}
+
+/// 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),
+}
+
+impl Entry {
+ /// If this is a property, return it.
+ fn property(&self) -> Option<&Property> {
+ match self {
+ Self::Property(property) => Some(property),
+ _ => None,
+ }
+ }
+
+ /// If this is a recipe, return it.
+ fn recipe(&self) -> Option<&Recipe> {
+ match self {
+ Self::Recipe(recipe) => Some(recipe),
+ _ => None,
+ }
+ }
+}
+
+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("]")
+ }
+}
+
+/// 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 Property {
+ /// Create a new property from a key-value pair.
+ fn new<'a, K: Key<'a>>(_: K, value: K::Value) -> Self {
+ Self {
+ key: TypeId::of::<K>(),
+ node: K::node(),
+ name: K::NAME,
+ value: Arc::new(Prehashed::new(value)),
+ scoped: false,
+ }
+ }
+
+ /// What kind of structure the property interrupts.
+ fn interruption(&self) -> Option<Interruption> {
+ if self.is_of::<PageNode>() {
+ Some(Interruption::Page)
+ } else if self.is_of::<ParNode>() {
+ Some(Interruption::Par)
+ } else {
+ 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 Property {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "{} = {:?}", self.name, self.value)?;
+ if self.scoped {
+ write!(f, " [scoped]")?;
+ }
+ Ok(())
+ }
+}
+
+impl PartialEq for Property {
+ fn eq(&self, other: &Self) -> bool {
+ self.key == other.key
+ && self.value.eq(&other.value)
+ && self.scoped == other.scoped
+ }
+}
+
+trait Bounds: Debug + Sync + Send + 'static {
+ fn as_any(&self) -> &dyn Any;
+}
+
+impl<T> Bounds for T
+where
+ T: Debug + Sync + Send + 'static,
+{
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+}
+
+/// Style property keys.
+///
+/// This trait is not intended to be implemented manually, but rather through
+/// the `#[node]` proc-macro.
+pub trait Key<'a>: Copy + '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;
+
+ /// The name of the property, used for debug printing.
+ const NAME: &'static str;
+
+ /// The type id of the node this property belongs to.
+ fn node() -> TypeId;
+
+ /// Compute an output value from a sequence of values belong to this key,
+ /// folding if necessary.
+ fn get(
+ chain: StyleChain<'a>,
+ values: impl Iterator<Item = &'a Self::Value>,
+ ) -> Self::Output;
+}
+
+/// 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))
+ }
+}
+
+impl<T: Resolve> Resolve for Smart<T> {
+ type Output = Smart<T::Output>;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ self.map(|v| v.resolve(styles))
+ }
+}
+
+impl<T: Resolve> Resolve for Spec<T> {
+ type Output = Spec<T::Output>;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ self.map(|v| v.resolve(styles))
+ }
+}
+
+impl<T: Resolve> Resolve for Sides<T> {
+ type Output = Sides<T::Output>;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ Sides {
+ left: self.left.resolve(styles),
+ right: self.right.resolve(styles),
+ top: self.top.resolve(styles),
+ bottom: self.bottom.resolve(styles),
+ }
+ }
+}
+
+impl<T> Resolve for Relative<T>
+where
+ T: Resolve + Numeric,
+ <T as Resolve>::Output: Numeric,
+{
+ type Output = Relative<<T as Resolve>::Output>;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ self.map(|abs| abs.resolve(styles))
+ }
+}
+
+/// 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;
+}
+
+impl<T> Fold for Option<T>
+where
+ T: Fold,
+ T::Output: Default,
+{
+ type Output = Option<T::Output>;
+
+ fn fold(self, outer: Self::Output) -> Self::Output {
+ self.map(|inner| inner.fold(outer.unwrap_or_default()))
+ }
+}
+
+impl<T> Fold for Smart<T>
+where
+ T: Fold,
+ T::Output: Default,
+{
+ type Output = Smart<T::Output>;
+
+ fn fold(self, outer: Self::Output) -> Self::Output {
+ self.map(|inner| inner.fold(outer.unwrap_or_default()))
+ }
+}
+
+/// A show rule recipe.
+#[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,
+}
+
+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 Debug for Recipe {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "Recipe for {} from {:?}", self.name, self.span)
+ }
+}
+
+/// 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!(),
+ };
+
+ 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
+ }
+ }
+}
+
+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> {
+ /// Create a new, empty style chain.
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// 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 output value of a style property.
+ ///
+ /// Returns the property's default value if no map in the chain contains an
+ /// entry for it. Also takes care of resolving and folding and returns
+ /// references where applicable.
+ pub fn get<K: Key<'a>>(self, key: K) -> K::Output {
+ K::get(self, self.values(key))
+ }
+
+ /// Realize a node with a user recipe.
+ pub fn realize(
+ self,
+ ctx: &mut Context,
+ node: &ShowNode,
+ ) -> TypResult<Option<Content>> {
+ let id = node.id();
+ if let Some(recipe) = self
+ .entries()
+ .filter_map(Entry::recipe)
+ .find(|recipe| recipe.node == id)
+ {
+ let dict = node.encode();
+ let args = Args::from_values(recipe.span, [Value::Dict(dict)]);
+ Ok(Some(recipe.func.call(ctx, args)?.cast().at(recipe.span)?))
+ } else {
+ Ok(None)
+ }
+ }
+}
+
+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)
+ {
+ let len = self.head.len();
+ self.head = &self.head[.. len - 1]
+ }
+ self
+ }
+
+ /// 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 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 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) -> Links<'a> {
+ Links(Some(self))
+ }
+}
+
+impl Debug for StyleChain<'_> {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ for entry in self.entries() {
+ writeln!(f, "{:?}", entry)?;
+ }
+ Ok(())
+ }
+}
+
+impl PartialEq for StyleChain<'_> {
+ fn eq(&self, other: &Self) -> bool {
+ let as_ptr = |s| s as *const _;
+ self.head.as_ptr() == other.head.as_ptr()
+ && self.head.len() == other.head.len()
+ && self.tail.map(as_ptr) == other.tail.map(as_ptr)
+ }
+}
+
+/// 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)
+ }
+}
+
+/// 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()
+ }
+
+ /// Number of items in the sequence.
+ pub fn len(&self) -> usize {
+ self.items.len()
+ }
+
+ /// Iterate over the contained maps. Note that zipping this with `items()`
+ /// does not yield the same result as calling `iter()` because this method
+ /// only returns maps once that are shared by consecutive items. This method
+ /// is designed for use cases where you want to check, for example, whether
+ /// any of the maps fulfills a specific property.
+ pub fn maps(&self) -> impl Iterator<Item = &StyleMap> {
+ self.maps.iter().map(|(map, _)| map)
+ }
+
+ /// 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)
+ }
+
+ /// Insert an element in the front. The element will share the style of the
+ /// current first element.
+ ///
+ /// This method has no effect if the vector is empty.
+ pub fn push_front(&mut self, item: T) {
+ if !self.maps.is_empty() {
+ self.items.insert(0, item);
+ self.maps[0].1 += 1;
+ }
+ }
+}
+
+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))
+ }
+
+ /// Iterate over the contained items.
+ pub fn items(&self) -> std::slice::Iter<'_, T> {
+ self.items.iter()
+ }
+
+ /// 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.links().count();
+ for &(mut chain, _) in iter {
+ let len = chain.links().count();
+ 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()
+ }
+}