diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-02-07 20:00:21 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-02-07 20:00:21 +0100 |
| commit | 68503b9a07b00bce3f4d377bcfe945452de815ea (patch) | |
| tree | 3719ef491b993c59b619ca215963000f4847e78f | |
| parent | 9730e785a885a4ab5fcc52ce705298654f82f9c2 (diff) | |
Redesigned template layout
44 files changed, 789 insertions, 633 deletions
@@ -933,6 +933,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6" [[package]] +name = "typed-arena" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae" + +[[package]] name = "typst" version = "0.1.0" dependencies = [ @@ -962,6 +968,7 @@ dependencies = [ "syntect", "tiny-skia", "ttf-parser", + "typed-arena", "typst-macros", "unicode-bidi", "unicode-segmentation", @@ -24,6 +24,7 @@ fxhash = "0.2" itertools = "0.10" once_cell = "1" serde = { version = "1", features = ["derive"] } +typed-arena = "2" # Text and font handling kurbo = "0.8" diff --git a/src/eval/collapse.rs b/src/eval/collapse.rs new file mode 100644 index 00000000..0e91cae6 --- /dev/null +++ b/src/eval/collapse.rs @@ -0,0 +1,109 @@ +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>, bool)>, + last: Last, +} + +/// What the last non-ignorant item was. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +enum Last { + Weak, + Destructive, + Supportive, +} + +impl<'a, T: Merge> 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, styles: StyleChain<'a>) { + if self.last == Last::Supportive { + self.staged.push((item, styles, true)); + 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, false)); + } + + /// 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, weak) in self.staged.drain(..) { + if !weak || supportive { + push_merging(&mut self.builder, item, styles); + } + } + } + + /// Push a new item into the style vector. + fn push(&mut self, item: T, styles: StyleChain<'a>) { + push_merging(&mut self.builder, item, styles); + } +} + +/// Push an item into a style-vec builder, trying to merging it with the +/// previous item. +fn push_merging<'a, T: Merge>( + builder: &mut StyleVecBuilder<'a, T>, + item: T, + styles: StyleChain<'a>, +) { + if let Some((prev_item, prev_styles)) = builder.last_mut() { + if styles == prev_styles { + if prev_item.merge(&item) { + return; + } + } + } + + builder.push(item, styles); +} + +impl<'a, T: Merge> Default for CollapsingBuilder<'a, T> { + fn default() -> Self { + Self::new() + } +} + +/// Defines if and how to merge two adjacent items in a [`CollapsingBuilder`]. +pub trait Merge { + /// Try to merge the items, returning whether they were merged. + /// + /// Defaults to not merging. + fn merge(&mut self, next: &Self) -> bool; +} diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 22bea7d1..98b0152c 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -10,6 +10,7 @@ mod value; mod styles; mod capture; mod class; +mod collapse; mod func; mod ops; mod scope; @@ -18,6 +19,7 @@ mod template; pub use array::*; pub use capture::*; pub use class::*; +pub use collapse::*; pub use dict::*; pub use func::*; pub use scope::*; diff --git a/src/eval/styles.rs b/src/eval/styles.rs index 14826aa8..863dcc6f 100644 --- a/src/eval/styles.rs +++ b/src/eval/styles.rs @@ -3,44 +3,10 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; -/// An item with associated styles. -#[derive(PartialEq, Clone, Hash)] -pub struct Styled<T> { - /// The item to apply styles to. - pub item: T, - /// The associated style map. - pub map: StyleMap, -} - -impl<T> Styled<T> { - /// Create a new instance from an item and a style map. - pub fn new(item: T, map: StyleMap) -> Self { - Self { item, map } - } - - /// Create a new instance with empty style map. - pub fn bare(item: T) -> Self { - Self { item, map: StyleMap::new() } - } - - /// Map the item with `f`. - pub fn map<F, U>(self, f: F) -> Styled<U> - where - F: FnOnce(T) -> U, - { - Styled { item: f(self.item), map: self.map } - } -} - -impl<T: Debug> Debug for Styled<T> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - self.map.fmt(f)?; - self.item.fmt(f) - } -} +use crate::library::{PageNode, ParNode}; /// A map of style properties. -#[derive(Default, Clone, Hash)] +#[derive(Default, Clone, PartialEq, Hash)] pub struct StyleMap(Vec<Entry>); impl StyleMap { @@ -93,7 +59,7 @@ impl StyleMap { *outer } else { StyleChain { - first: Link::Map(self), + link: Some(Link::Map(self)), outer: Some(outer), } } @@ -103,31 +69,16 @@ impl StyleMap { /// equivalent to the style chain created by /// `self.chain(StyleChain::new(outer))`. /// - /// This is useful in the evaluation phase while building nodes and their - /// style maps, whereas `chain` would be used during layouting to combine - /// immutable style maps from different levels of the hierarchy. + /// 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.0.splice(0 .. 0, outer.0.clone()); } - /// Subtract `other` from `self` in-place, keeping only styles that are in - /// `self` but not in `other`. - pub fn erase(&mut self, other: &Self) { - self.0.retain(|x| !other.0.contains(x)); - } - - /// Intersect `self` with `other` in-place, keeping only styles that are - /// both in `self` and `other`. - pub fn intersect(&mut self, other: &Self) { - self.0.retain(|x| other.0.contains(x)); - } - - /// Whether two style maps are equal when filtered down to properties of the - /// node `T`. - pub fn compatible<T: 'static>(&self, other: &Self) -> bool { - let f = |entry: &&Entry| entry.is_of::<T>(); - self.0.iter().filter(f).count() == other.0.iter().filter(f).count() - && self.0.iter().filter(f).all(|x| other.0.contains(x)) + /// The highest-level interruption of the map. + pub fn interruption(&self) -> Option<Interruption> { + self.0.iter().filter_map(|entry| entry.interruption()).max() } } @@ -140,10 +91,13 @@ impl Debug for StyleMap { } } -impl PartialEq for StyleMap { - fn eq(&self, other: &Self) -> bool { - self.0.len() == other.0.len() && self.0.iter().all(|x| other.0.contains(x)) - } +/// 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. @@ -153,10 +107,10 @@ impl PartialEq for StyleMap { /// 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(Clone, Copy, Hash)] +#[derive(Default, Clone, Copy, Hash)] pub struct StyleChain<'a> { - /// The first link in the chain. - first: Link<'a>, + /// The first link of this chain. + link: Option<Link<'a>>, /// The remaining links in the chain. outer: Option<&'a Self>, } @@ -173,10 +127,52 @@ enum Link<'a> { impl<'a> StyleChain<'a> { /// Start a new style chain with a root map. - pub fn new(first: &'a StyleMap) -> Self { - Self { first: Link::Map(first), outer: None } + pub fn new(map: &'a StyleMap) -> Self { + Self { link: Some(Link::Map(map)), outer: None } + } + + /// The number of links in the chain. + pub fn len(self) -> usize { + self.links().count() + } + + /// Convert to an owned style map. + /// + /// Panics if the chain contains barrier links. + pub fn to_map(self) -> StyleMap { + let mut suffix = StyleMap::new(); + for link in self.links() { + match link { + Link::Map(map) => suffix.apply(map), + Link::Barrier(_) => panic!("chain contains barrier"), + } + } + suffix + } + + /// Build a style map from the suffix (all links beyond the `len`) of the + /// chain. + /// + /// Panics if the suffix contains barrier links. + pub fn suffix(self, len: usize) -> StyleMap { + let mut suffix = StyleMap::new(); + let remove = self.len().saturating_sub(len); + for link in self.links().take(remove) { + match link { + Link::Map(map) => suffix.apply(map), + Link::Barrier(_) => panic!("suffix contains barrier"), + } + } + suffix } + /// Remove the last link from the chain. + pub fn pop(&mut self) { + *self = self.outer.copied().unwrap_or_default(); + } +} + +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 @@ -235,17 +231,19 @@ impl<'a> StyleChain<'a> { pub fn barred<'b>(&'b self, node: TypeId) -> StyleChain<'b> { if self .maps() - .any(|map| map.0.iter().any(|entry| entry.scoped && entry.is_of_same(node))) + .any(|map| map.0.iter().any(|entry| entry.scoped && entry.is_of_id(node))) { StyleChain { - first: Link::Barrier(node), + link: Some(Link::Barrier(node)), outer: Some(self), } } else { *self } } +} +impl<'a> StyleChain<'a> { /// Iterate over all values for the given property in the chain. fn values<P: Property>(self, _: P) -> impl Iterator<Item = &'a P::Value> { let mut depth = 0; @@ -263,16 +261,6 @@ impl<'a> StyleChain<'a> { }) } - /// 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 { first, outer } = cursor?; - cursor = outer.copied(); - Some(first) - }) - } - /// Iterate over the map links of the chain. fn maps(self) -> impl Iterator<Item = &'a StyleMap> { self.links().filter_map(|link| match link { @@ -280,6 +268,16 @@ impl<'a> StyleChain<'a> { Link::Barrier(_) => None, }) } + + /// Iterate over the links of the chain. + fn links(self) -> impl Iterator<Item = Link<'a>> { + let mut cursor = Some(self); + std::iter::from_fn(move || { + let Self { link, outer } = cursor?; + cursor = outer.copied(); + link + }) + } } impl Debug for StyleChain<'_> { @@ -300,66 +298,154 @@ impl Debug for Link<'_> { } } -/// An entry for a single style property. -#[derive(Clone)] -struct Entry { - pair: Arc<dyn Bounds>, - scoped: bool, +impl PartialEq for StyleChain<'_> { + fn eq(&self, other: &Self) -> bool { + let as_ptr = |s| s as *const _; + self.link == other.link && self.outer.map(as_ptr) == other.outer.map(as_ptr) + } } -impl Entry { - fn new<P: Property>(key: P, value: P::Value) -> Self { - Self { - pair: Arc::new((key, value)), - scoped: false, +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, } } +} - fn is<P: Property>(&self) -> bool { - self.pair.style_id() == TypeId::of::<P>() +/// 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() } - fn is_of<T: 'static>(&self) -> bool { - self.pair.node_id() == TypeId::of::<T>() + /// Iterate over the contained items. + pub fn items(&self) -> std::slice::Iter<'_, T> { + self.items.iter() } - fn is_of_same(&self, node: TypeId) -> bool { - self.pair.node_id() == node + /// 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) } +} - fn downcast<P: Property>(&self) -> Option<&P::Value> { - self.pair.as_any().downcast_ref() +impl<T> Default for StyleVec<T> { + fn default() -> Self { + Self { items: vec![], maps: vec![] } } } -impl Debug for Entry { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("#[")?; - self.pair.dyn_fmt(f)?; - if self.scoped { - f.write_str(" (scoped)")?; - } - f.write_str("]") +impl<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() } } -impl PartialEq for Entry { - fn eq(&self, other: &Self) -> bool { - self.pair.dyn_eq(other) && self.scoped == other.scoped +/// Assists in the construction of a [`StyleVec`]. +pub struct StyleVecBuilder<'a, T> { + items: Vec<T>, + chains: Vec<(StyleChain<'a>, usize)>, +} + +impl<'a, T> StyleVecBuilder<'a, T> { + /// Create a new style-vec builder. + pub fn new() -> Self { + Self { items: vec![], chains: vec![] } + } + + /// Push a new item into the style vector. + pub fn push(&mut self, item: T, styles: StyleChain<'a>) { + self.items.push(item); + + if let Some((prev, count)) = self.chains.last_mut() { + if *prev == styles { + *count += 1; + return; + } + } + + self.chains.push((styles, 1)); + } + + /// Access the last item mutably and its chain by value. + pub fn last_mut(&mut self) -> Option<(&mut T, StyleChain<'a>)> { + let item = self.items.last_mut()?; + let chain = self.chains.last()?.0; + Some((item, chain)) + } + + /// Finish building, returning a pair of two things: + /// - a style vector of items with the non-shared styles + /// - a shared prefix chain of styles that apply to all items + pub fn finish(self) -> (StyleVec<T>, StyleChain<'a>) { + let mut iter = self.chains.iter(); + let mut trunk = match iter.next() { + Some(&(chain, _)) => chain, + None => return Default::default(), + }; + + let mut shared = trunk.len(); + for &(mut chain, _) in iter { + let len = chain.len(); + if len < shared { + for _ in 0 .. shared - len { + trunk.pop(); + } + shared = len; + } else if len > shared { + for _ in 0 .. len - shared { + chain.pop(); + } + } + + while shared > 0 && chain != trunk { + trunk.pop(); + chain.pop(); + shared -= 1; + } + } + + let maps = self + .chains + .into_iter() + .map(|(chain, count)| (chain.suffix(shared), count)) + .collect(); + + (StyleVec { items: self.items, maps }, trunk) } } -impl Hash for Entry { - fn hash<H: Hasher>(&self, state: &mut H) { - state.write_u64(self.pair.hash64()); - state.write_u8(self.scoped as u8); +impl<'a, T> Default for StyleVecBuilder<'a, T> { + fn default() -> Self { + Self::new() } } /// Style property keys. /// /// This trait is not intended to be implemented manually, but rather through -/// the `#[properties]` proc-macro. +/// the `#[class]` proc-macro. pub trait Property: Sync + Send + 'static { /// The type of value that is returned when getting this property from a /// style map. For example, this could be [`Length`](crate::geom::Length) @@ -381,7 +467,7 @@ pub trait Property: Sync + Send + 'static { /// A static reference to the default value of the property. /// /// This is automatically implemented through lazy-initialization in the - /// `#[properties]` macro. This way, expensive defaults don't need to be + /// `#[class]` macro. This way, expensive defaults don't need to be /// recreated all the time. fn default_ref() -> &'static Self::Value; @@ -398,6 +484,72 @@ pub trait Property: Sync + Send + 'static { /// Marker trait that indicates that a property doesn't need folding. pub trait Nonfolding {} +/// An entry for a single style property. +#[derive(Clone)] +struct Entry { + pair: Arc<dyn Bounds>, + scoped: bool, +} + +impl Entry { + fn new<P: Property>(key: P, value: P::Value) -> Self { + Self { + pair: Arc::new((key, value)), + scoped: false, + } + } + + fn is<P: Property>(&self) -> bool { + self.pair.style_id() == TypeId::of::<P>() + } + + fn is_of<T: 'static>(&self) -> bool { + self.pair.node_id() == TypeId::of::<T>() + } + + fn is_of_id(&self, node: TypeId) -> bool { + self.pair.node_id() == node + } + + fn downcast<P: Property>(&self) -> Option<&P::Value> { + self.pair.as_any().downcast_ref() + } + + fn interruption(&self) -> Option<Interruption> { + if self.is_of::<PageNode>() { + Some(Interruption::Page) + } else if self.is_of::<ParNode>() { + Some(Interruption::Par) + } else { + None + } + } +} + +impl Debug for Entry { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_str("#[")?; + self.pair.dyn_fmt(f)?; + if self.scoped { + f.write_str(" (scoped)")?; + } + f.write_str("]") + } +} + +impl PartialEq for Entry { + fn eq(&self, other: &Self) -> bool { + self.pair.dyn_eq(other) && self.scoped == other.scoped + } +} + +impl Hash for Entry { + fn hash<H: Hasher>(&self, state: &mut H) { + state.write_u64(self.pair.hash64()); + state.write_u8(self.scoped as u8); + } +} + /// This trait is implemented for pairs of zero-sized property keys and their /// value types below. Although it is zero-sized, the property `P` must be part /// of the implementing type so that we can use it in the methods (it must be a diff --git a/src/eval/template.rs b/src/eval/template.rs index b15215ce..a6693c84 100644 --- a/src/eval/template.rs +++ b/src/eval/template.rs @@ -1,11 +1,12 @@ -use std::convert::TryFrom; use std::fmt::Debug; use std::hash::Hash; use std::iter::Sum; use std::mem; use std::ops::{Add, AddAssign}; -use super::{Property, StyleMap, Styled}; +use typed_arena::Arena; + +use super::{CollapsingBuilder, Interruption, Property, StyleMap, StyleVecBuilder}; use crate::diag::StrResult; use crate::layout::{Layout, PackedNode}; use crate::library::prelude::*; @@ -50,16 +51,16 @@ pub enum Template { Horizontal(SpacingKind), /// Plain text. Text(EcoString), - /// An inline node. + /// An inline-level node. Inline(PackedNode), /// A paragraph break. Parbreak, + /// A column break. + Colbreak, /// Vertical spacing. Vertical(SpacingKind), - /// A block node. + /// A block-level node. Block(PackedNode), - /// A column break. - Colbreak, /// A page break. Pagebreak, /// A page node. @@ -95,12 +96,11 @@ impl Template { /// Layout this template into a collection of pages. pub fn layout(&self, ctx: &mut Context) -> Vec<Arc<Frame>> { let (mut ctx, styles) = LayoutContext::new(ctx); - let mut packer = Packer::new(true); - packer.walk(self.clone(), StyleMap::new()); - packer - .into_root() + let (pages, shared) = Builder::build_pages(self); + let styles = shared.chain(&styles); + pages .iter() - .flat_map(|styled| styled.item.layout(&mut ctx, styled.map.chain(&styles))) + .flat_map(|(page, map)| page.layout(&mut ctx, map.chain(&styles))) .collect() } @@ -159,13 +159,13 @@ impl Debug for Template { 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::Colbreak => f.pad("Colbreak"), Self::Pagebreak => f.pad("Pagebreak"), Self::Page(page) => page.fmt(f), Self::Styled(sub, map) => { @@ -220,338 +220,172 @@ impl Layout for Template { regions: &Regions, styles: StyleChain, ) -> Vec<Constrained<Arc<Frame>>> { - let mut packer = Packer::new(false); - packer.walk(self.clone(), StyleMap::new()); - packer.into_block().layout(ctx, regions, styles) + let (flow, shared) = Builder::build_flow(self); + flow.layout(ctx, regions, shared.chain(&styles)) } fn pack(self) -> PackedNode { - if let Template::Block(packed) = self { - packed - } else { - PackedNode::new(self) + match self { + Template::Block(node) => node, + other => PackedNode::new(other), } } } -/// Packs a [`Template`] into a flow or root node. -struct Packer { - /// Whether this packer produces a root node. - top: bool, - /// The accumulated page nodes. - pages: Vec<Styled<PageNode>>, - /// The accumulated flow children. - flow: Builder<Styled<FlowChild>>, - /// The accumulated paragraph children. - par: Builder<Styled<ParChild>>, +/// Builds a flow or page nodes from a template. +struct Builder<'a> { + /// An arena where intermediate style chains are stored. + arena: &'a Arena<StyleChain<'a>>, + /// The already built page runs. + pages: Option<StyleVecBuilder<'a, PageNode>>, + /// 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, } -impl Packer { - /// Start a new template-packing session. - fn new(top: bool) -> Self { - Self { - top, - pages: vec![], - flow: Builder::default(), - par: Builder::default(), - } +impl<'a> Builder<'a> { + /// Build page runs from a template. + fn build_pages(template: &Template) -> (StyleVec<PageNode>, StyleMap) { + let arena = Arena::new(); + + let mut builder = Builder::prepare(&arena, true); + builder.process(template, StyleChain::default()); + builder.finish_page(true, false, StyleChain::default()); + + let (pages, shared) = builder.pages.unwrap().finish(); + (pages, shared.to_map()) } - /// Finish up and return the resulting flow. - fn into_block(mut self) -> PackedNode { - self.parbreak(None, false); - FlowNode(self.flow.children).pack() + /// Build a subflow from a template. + fn build_flow(template: &Template) -> (FlowNode, StyleMap) { + let arena = Arena::new(); + + let mut builder = Builder::prepare(&arena, false); + builder.process(template, StyleChain::default()); + builder.finish_par(); + + let (flow, shared) = builder.flow.finish(); + (FlowNode(flow), shared.to_map()) } - /// Finish up and return the resulting root node. - fn into_root(mut self) -> Vec<Styled<PageNode>> { - self.pagebreak(); - self.pages + /// Prepare the builder. + fn prepare(arena: &'a Arena<StyleChain<'a>>, top: bool) -> Self { + Self { + arena, + pages: top.then(|| StyleVecBuilder::new()), + flow: CollapsingBuilder::new(), + par: CollapsingBuilder::new(), + keep_next: true, + } } - /// Consider a template with the given styles. - fn walk(&mut self, template: Template, styles: StyleMap) { + /// Process a template. + fn process(&mut self, template: &'a Template, styles: StyleChain<'a>) { match template { Template::Space => { - // A text space is "soft", meaning that it can be eaten up by - // adjacent line breaks or explicit spacings. - self.par.last.soft(Styled::new(ParChild::text(' '), styles), false); + self.par.weak(ParChild::Text(' '.into()), styles); } Template::Linebreak => { - // A line break eats up surrounding text spaces. - self.par.last.hard(); - self.push_inline(Styled::new(ParChild::text('\n'), styles)); - self.par.last.hard(); + self.par.destructive(ParChild::Text('\n'.into()), styles); } - Template::Parbreak => { - // An explicit paragraph break is styled according to the active - // styles (`Some(_)`) whereas paragraph breaks forced by - // incompatibility take their styles from the preceding - // paragraph. - self.parbreak(Some(styles), true); - } - Template::Colbreak => { - // Explicit column breaks end the current paragraph and then - // discards the paragraph break. - self.parbreak(None, false); - self.make_flow_compatible(&styles); - self.flow.children.push(Styled::new(FlowChild::Skip, styles)); - self.flow.last.hard(); - } - Template::Pagebreak => { - // We must set the flow styles after the page break such that an - // empty page created by two page breaks in a row has styles at - // all. - self.pagebreak(); - self.flow.styles = styles; + Template::Horizontal(kind) => { + let child = ParChild::Spacing(*kind); + if kind.is_fractional() { + self.par.destructive(child, styles); + } else { + self.par.ignorant(child, styles); + } } Template::Text(text) => { - self.push_inline(Styled::new(ParChild::text(text), styles)); + self.par.supportive(ParChild::Text(text.clone()), styles); } - Template::Horizontal(kind) => { - // Just like a line break, explicit horizontal spacing eats up - // surrounding text spaces. - self.par.last.hard(); - self.push_inline(Styled::new(ParChild::Spacing(kind), styles)); - self.par.last.hard(); + Template::Inline(node) => { + self.par.supportive(ParChild::Node(node.clone()), styles); } - Template::Vertical(kind) => { - // Explicit vertical spacing ends the current paragraph and then - // discards the paragraph break. - self.parbreak(None, false); - self.make_flow_compatible(&styles); - self.flow.children.push(Styled::new(FlowChild::Spacing(kind), styles)); - self.flow.last.hard(); + Template::Parbreak => { + self.finish_par(); + self.flow.weak(FlowChild::Parbreak, styles); } - Template::Inline(inline) => { - self.push_inline(Styled::new(ParChild::Node(inline), styles)); + Template::Colbreak => { + self.finish_par(); + self.flow.destructive(FlowChild::Colbreak, styles); } - Template::Block(block) => { - self.push_block(Styled::new(block, styles)); + Template::Vertical(kind) => { + self.finish_par(); + let child = FlowChild::Spacing(*kind); + if kind.is_fractional() { + self.flow.destructive(child, styles); + } else { + self.flow.ignorant(child, styles); + } } - Template::Page(page) => { - if self.top { - self.pagebreak(); - self.pages.push(Styled::new(page, styles)); + Template::Block(node) => { + self.finish_par(); + let child = FlowChild::Node(node.clone()); + if node.is::<PlaceNode>() { + self.flow.ignorant(child, styles); } else { - self.push_block(Styled::new(page.0, styles)); + self.flow.supportive(child, styles); } } - Template::Styled(template, mut map) => { - map.apply(&styles); - self.walk(*template, map); + Template::Pagebreak => { + self.finish_page(true, true, styles); } - Template::Sequence(seq) => { - // For a list of templates, we apply the list's styles to each - // templates individually. - for item in seq { - self.walk(item, styles.clone()); + Template::Page(page) => { + self.finish_page(false, false, styles); + if let Some(pages) = &mut self.pages { + pages.push(page.clone(), styles); } } - } - } - - /// Insert an inline-level element into the current paragraph. - fn push_inline(&mut self, child: Styled<ParChild>) { - // The child's map must be both compatible with the current page and the - // current paragraph. - self.make_flow_compatible(&child.map); - self.make_par_compatible(&child.map); - - if let Some(styled) = self.par.last.any() { - self.push_coalescing(styled); - } - - self.push_coalescing(child); - self.par.last.any(); - } - - /// Push a paragraph child, coalescing text nodes with compatible styles. - fn push_coalescing(&mut self, child: Styled<ParChild>) { - if let ParChild::Text(right) = &child.item { - if let Some(Styled { item: ParChild::Text(left), map }) = - self.par.children.last_mut() - { - if child.map.compatible::<TextNode>(map) { - left.0.push_str(&right.0); - return; + Template::Styled(sub, map) => { + let interruption = map.interruption(); + match interruption { + Some(Interruption::Page) => self.finish_page(false, true, styles), + Some(Interruption::Par) => self.finish_par(), + None => {} } - } - } - self.par.children.push(child); - } - - /// Insert a block-level element into the current flow. - fn push_block(&mut self, node: Styled<PackedNode>) { - let placed = node.item.is::<PlaceNode>(); - - self.parbreak(Some(node.map.clone()), false); - self.make_flow_compatible(&node.map); - self.flow.children.extend(self.flow.last.any()); - self.flow.children.push(node.map(FlowChild::Node)); - self.parbreak(None, false); - - // Prevent paragraph spacing between the placed node and the paragraph - // below it. - if placed { - self.flow.last.hard(); - } - } + let outer = self.arena.alloc(styles); + let styles = map.chain(outer); + self.process(sub, styles); - /// Advance to the next paragraph. - fn parbreak(&mut self, break_styles: Option<StyleMap>, important: bool) { - // Erase any styles that will be inherited anyway. - let Builder { mut children, styles, .. } = mem::take(&mut self.par); - for Styled { map, .. } in &mut children { - map.erase(&styles); - } - - // We don't want empty paragraphs. - if !children.is_empty() { - // The paragraph's children are all compatible with the page, so the - // paragraph is too, meaning we don't need to check or intersect - // anything here. - let par = ParNode(children).pack(); - self.flow.children.extend(self.flow.last.any()); - self.flow.children.push(Styled::new(FlowChild::Node(par), styles)); - } - - // Actually styled breaks have precedence over whatever was before. - if break_styles.is_some() { - if let Last::Soft(_, false) = self.flow.last { - self.flow.last = Last::Any; + match interruption { + Some(Interruption::Page) => self.finish_page(true, false, styles), + Some(Interruption::Par) => self.finish_par(), + None => {} + } } - } - - // For explicit paragraph breaks, `break_styles` is already `Some(_)`. - // For page breaks due to incompatibility, we fall back to the styles - // of the preceding thing. - let break_styles = break_styles - .or_else(|| self.flow.children.last().map(|styled| styled.map.clone())) - .unwrap_or_default(); - - // Insert paragraph spacing. - self.flow - .last - .soft(Styled::new(FlowChild::Break, break_styles), important); - } - - /// Advance to the next page. - fn pagebreak(&mut self) { - if self.top { - self.parbreak(None, false); - - // Take the flow and erase any styles that will be inherited anyway. - let Builder { mut children, styles, .. } = mem::take(&mut self.flow); - for Styled { map, .. } in &mut children { - map.erase(&styles); + Template::Sequence(seq) => { + for sub in seq { + self.process(sub, styles); + } } - - let flow = FlowNode(children).pack(); - self.pages.push(Styled::new(PageNode(flow), styles)); - } - } - - /// Break to a new paragraph if the `styles` contain paragraph styles that - /// are incompatible with the current paragraph. - fn make_par_compatible(&mut self, styles: &StyleMap) { - if self.par.children.is_empty() { - self.par.styles = styles.clone(); - return; - } - - if !self.par.styles.compatible::<ParNode>(styles) { - self.parbreak(Some(styles.clone()), false); - self.par.styles = styles.clone(); - return; } - - self.par.styles.intersect(styles); } - /// Break to a new page if the `styles` contain page styles that are - /// incompatible with the current flow. - fn make_flow_compatible(&mut self, styles: &StyleMap) { - if self.flow.children.is_empty() && self.par.children.is_empty() { - self.flow.styles = styles.clone(); - return; - } - - if self.top && !self.flow.styles.compatible::<PageNode>(styles) { - self.pagebreak(); - self.flow.styles = styles.clone(); - return; + /// Finish the currently built paragraph. + fn finish_par(&mut self) { + let (par, shared) = mem::take(&mut self.par).finish(); + if !par.is_empty() { + let node = ParNode(par).pack(); + self.flow.supportive(FlowChild::Node(node), shared); } - - self.flow.styles.intersect(styles); } -} -/// Container for building a flow or paragraph. -struct Builder<T> { - /// The intersection of the style properties of all `children`. - styles: StyleMap, - /// The accumulated flow or paragraph children. - children: Vec<T>, - /// The kind of thing that was last added. - last: Last<T>, -} - -impl<T> Default for Builder<T> { - fn default() -> Self { - Self { - styles: StyleMap::new(), - children: vec![], - last: Last::None, - } - } -} - -/// The kind of child that was last added to a flow or paragraph. A small finite -/// state machine used to coalesce spaces. -/// -/// Soft children can only exist when surrounded by `Any` children. Not at the -/// start, end or next to hard children. This way, spaces at start and end of -/// paragraphs and next to `#h(..)` goes away. -enum Last<N> { - /// Start state, nothing there. - None, - /// Text or a block node or something. - Any, - /// Hard children: Linebreaks and explicit spacing. - Hard, - /// Soft children: Word spaces and paragraph breaks. These are saved here - /// temporarily and then applied once an `Any` child appears. The boolean - /// says whether this soft child is "important" and preferrable to other soft - /// nodes (that is the case for explicit paragraph breaks). - Soft(N, bool), -} - -impl<N> Last<N> { - /// Transition into the `Any` state and return a soft child to really add - /// now if currently in `Soft` state. - fn any(&mut self) -> Option<N> { - match mem::replace(self, Self::Any) { - Self::Soft(soft, _) => Some(soft), - _ => None, - } - } - - /// Transition into the `Soft` state, but only if in `Any`. Otherwise, the - /// soft child is discarded. - fn soft(&mut self, soft: N, important: bool) { - if matches!( - (&self, important), - (Self::Any, _) | (Self::Soft(_, false), true) - ) { - *self = Self::Soft(soft, important); + /// Finish the currently built page run. + fn finish_page(&mut self, keep_last: bool, keep_next: bool, styles: StyleChain<'a>) { + self.finish_par(); + if let Some(pages) = &mut self.pages { + let (flow, shared) = 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); + } } - } - - /// Transition into the `Hard` state, discarding a possibly existing soft - /// child and preventing further soft nodes from being added. - fn hard(&mut self) { - *self = Self::Hard; + self.keep_next = keep_next; } } diff --git a/src/library/flow.rs b/src/library/flow.rs index 227a7a35..bba296a0 100644 --- a/src/library/flow.rs +++ b/src/library/flow.rs @@ -1,16 +1,27 @@ //! A flow of paragraphs and other block-level nodes. -use std::fmt::{self, Debug, Formatter}; - use super::prelude::*; use super::{AlignNode, ParNode, PlaceNode, SpacingKind, TextNode}; -/// A vertical flow of content consisting of paragraphs and other layout nodes. +/// Arrange spacing, paragraphs and other block-level nodes into a flow. /// /// This node is reponsible for layouting both the top-level content flow and /// the contents of boxes. #[derive(Hash)] -pub struct FlowNode(pub Vec<Styled<FlowChild>>); +pub struct FlowNode(pub StyleVec<FlowChild>); + +/// A child of a flow node. +#[derive(Hash)] +pub enum FlowChild { + /// A paragraph / block break. + Parbreak, + /// A column / region break. + Colbreak, + /// Vertical spacing between other children. + Spacing(SpacingKind), + /// An arbitrary block-level node. + Node(PackedNode), +} impl Layout for FlowNode { fn layout( @@ -19,45 +30,58 @@ impl Layout for FlowNode { regions: &Regions, styles: StyleChain, ) -> Vec<Constrained<Arc<Frame>>> { - FlowLayouter::new(self, regions.clone()).layout(ctx, styles) + let mut layouter = FlowLayouter::new(regions); + + for (child, map) in self.0.iter() { + let styles = map.chain(&styles); + match child { + FlowChild::Parbreak => { + let em = styles.get(TextNode::SIZE).abs; + let amount = styles.get(ParNode::SPACING).resolve(em); + layouter.layout_spacing(SpacingKind::Linear(amount.into())); + } + FlowChild::Colbreak => { + layouter.finish_region(); + } + FlowChild::Spacing(kind) => { + layouter.layout_spacing(*kind); + } + FlowChild::Node(ref node) => { + layouter.layout_node(ctx, node, styles); + } + } + } + + layouter.finish() + } +} + +impl Merge for FlowChild { + fn merge(&mut self, _: &Self) -> bool { + false } } impl Debug for FlowNode { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.write_str("Flow ")?; - f.debug_list().entries(&self.0).finish() + self.0.fmt(f) } } -/// A child of a flow node. -#[derive(Hash)] -pub enum FlowChild { - /// A paragraph/block break. - Break, - /// Skip the rest of the region and move to the next. - Skip, - /// Vertical spacing between other children. - Spacing(SpacingKind), - /// An arbitrary node. - Node(PackedNode), -} - impl Debug for FlowChild { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { - Self::Break => f.pad("Break"), - Self::Skip => f.pad("Skip"), - Self::Spacing(kind) => kind.fmt(f), + Self::Parbreak => f.pad("Parbreak"), + Self::Colbreak => f.pad("Colbreak"), + Self::Spacing(kind) => write!(f, "{:?}", kind), Self::Node(node) => node.fmt(f), } } } /// Performs flow layout. -struct FlowLayouter<'a> { - /// The children of the flow. - children: &'a [Styled<FlowChild>], +pub struct FlowLayouter { /// The regions to layout children into. regions: Regions, /// Whether the flow should expand to fill the region. @@ -69,6 +93,8 @@ struct FlowLayouter<'a> { used: Size, /// The sum of fractional ratios in the current region. fr: Fractional, + /// Whether to add leading before the next node. + leading: bool, /// Spacing and layouted nodes. items: Vec<FlowItem>, /// Finished frames for previous regions. @@ -87,98 +113,64 @@ enum FlowItem { Placed(Arc<Frame>), } -impl<'a> FlowLayouter<'a> { +impl FlowLayouter { /// Create a new flow layouter. - fn new(flow: &'a FlowNode, mut regions: Regions) -> Self { + pub fn new(regions: &Regions) -> Self { let expand = regions.expand; let full = regions.current; // Disable vertical expansion for children. + let mut regions = regions.clone(); regions.expand.y = false; Self { - children: &flow.0, regions, expand, full, used: Size::zero(), fr: Fractional::zero(), + leading: false, items: vec![], finished: vec![], } } - /// Layout all children. - fn layout( - mut self, - ctx: &mut LayoutContext, - styles: StyleChain, - ) -> Vec<Constrained<Arc<Frame>>> { - for styled in self.children { - let styles = styled.map.chain(&styles); - match styled.item { - FlowChild::Break => { - let em = styles.get(TextNode::SIZE).abs; - let amount = styles.get(ParNode::SPACING).resolve(em); - self.layout_absolute(amount.into()); - } - FlowChild::Skip => { - self.finish_region(); - } - FlowChild::Spacing(kind) => { - self.layout_spacing(kind); - } - FlowChild::Node(ref node) => { - if self.regions.is_full() { - self.finish_region(); - } - - self.layout_node(ctx, node, styles); - } - } - } - - if self.expand.y { - while self.regions.backlog.len() > 0 { - self.finish_region(); - } - } - - self.finish_region(); - self.finished - } - /// Layout spacing. - fn layout_spacing(&mut self, spacing: SpacingKind) { + pub fn layout_spacing(&mut self, spacing: SpacingKind) { match spacing { - SpacingKind::Linear(v) => self.layout_absolute(v), + SpacingKind::Linear(v) => { + // Resolve the linear and limit it to the remaining space. + let resolved = v.resolve(self.full.y); + let limited = resolved.min(self.regions.current.y); + self.regions.current.y -= limited; + self.used.y += limited; + self.items.push(FlowItem::Absolute(resolved)); + } SpacingKind::Fractional(v) => { self.items.push(FlowItem::Fractional(v)); self.fr += v; + self.leading = false; } } } - /// Layout absolute spacing. - fn layout_absolute(&mut self, amount: Linear) { - // Resolve the linear, limiting it to the remaining available space. - let resolved = amount.resolve(self.full.y); - let limited = resolved.min(self.regions.current.y); - self.regions.current.y -= limited; - self.used.y += limited; - self.items.push(FlowItem::Absolute(resolved)); - } - /// Layout a node. - fn layout_node( + pub fn layout_node( &mut self, ctx: &mut LayoutContext, node: &PackedNode, styles: StyleChain, ) { + // Don't even try layouting into a full region. + if self.regions.is_full() { + self.finish_region(); + } + // Placed nodes that are out of flow produce placed items which aren't // aligned later. + let mut is_placed = false; if let Some(placed) = node.downcast::<PlaceNode>() { + is_placed = true; if placed.out_of_flow() { let frame = node.layout(ctx, &self.regions, styles).remove(0); self.items.push(FlowItem::Placed(frame.item)); @@ -186,6 +178,13 @@ impl<'a> FlowLayouter<'a> { } } + // Add leading. + if self.leading { + let em = styles.get(TextNode::SIZE).abs; + let amount = styles.get(ParNode::LEADING).resolve(em); + self.layout_spacing(SpacingKind::Linear(amount.into())); + } + // How to align the node. let aligns = Spec::new( // For non-expanding paragraphs it is crucial that we align the @@ -211,10 +210,12 @@ impl<'a> FlowLayouter<'a> { self.finish_region(); } } + + self.leading = !is_placed; } /// Finish the frame for one region. - fn finish_region(&mut self) { + pub fn finish_region(&mut self) { // Determine the size of the flow in this region dependening on whether // the region expands. let mut size = self.expand.select(self.full, self.used); @@ -263,6 +264,19 @@ impl<'a> FlowLayouter<'a> { self.full = self.regions.current; self.used = Size::zero(); self.fr = Fractional::zero(); + self.leading = false; self.finished.push(output.constrain(cts)); } + + /// Finish layouting and return the resulting frames. + pub fn finish(mut self) -> Vec<Constrained<Arc<Frame>>> { + if self.expand.y { + while self.regions.backlog.len() > 0 { + self.finish_region(); + } + } + + self.finish_region(); + self.finished + } } diff --git a/src/library/mod.rs b/src/library/mod.rs index 9e3bf18b..ef0fa016 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -68,8 +68,8 @@ prelude! { pub use crate::diag::{At, TypResult}; pub use crate::eval::{ - Args, Construct, EvalContext, Template, Property, Scope, Set, Smart, StyleChain, - StyleMap, Styled, Value, + Args, Construct, EvalContext, Merge, Property, Scope, Set, Smart, StyleChain, + StyleMap, StyleVec, Template, Value, }; pub use crate::frame::*; pub use crate::geom::*; diff --git a/src/library/par.rs b/src/library/par.rs index 4568a214..962834f2 100644 --- a/src/library/par.rs +++ b/src/library/par.rs @@ -1,6 +1,5 @@ //! Paragraph layout. -use std::fmt::{self, Debug, Formatter}; use std::sync::Arc; use itertools::Either; @@ -11,9 +10,20 @@ use super::prelude::*; use super::{shape, ShapedText, SpacingKind, TextNode}; use crate::util::{ArcExt, EcoString, RangeExt, SliceExt}; -/// Arrange text, spacing and inline nodes into a paragraph. +/// Arrange text, spacing and inline-level nodes into a paragraph. #[derive(Hash)] -pub struct ParNode(pub Vec<Styled<ParChild>>); +pub struct ParNode(pub StyleVec<ParChild>); + +/// A uniformly styled atomic piece of a paragraph. +#[derive(Hash)] +pub enum ParChild { + /// A chunk of text. + Text(EcoString), + /// Horizontal spacing between other children. + Spacing(SpacingKind), + /// An arbitrary inline-level node. + Node(PackedNode), +} #[class] impl ParNode { @@ -23,8 +33,8 @@ impl ParNode { pub const ALIGN: Align = Align::Left; /// The spacing between lines (dependent on scaled font size). pub const LEADING: Linear = Relative::new(0.65).into(); - /// The spacing between paragraphs (dependent on scaled font size). - pub const SPACING: Linear = Relative::new(1.2).into(); + /// The extra spacing between paragraphs (dependent on scaled font size). + pub const SPACING: Linear = Relative::new(0.55).into(); fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> { // The paragraph constructor is special: It doesn't create a paragraph @@ -116,9 +126,9 @@ impl ParNode { /// The string representation of each child. fn strings(&self) -> impl Iterator<Item = &str> { - self.0.iter().map(|styled| match &styled.item { + self.0.items().map(|child| match child { + ParChild::Text(text) => text, ParChild::Spacing(_) => " ", - ParChild::Text(text) => &text.0, ParChild::Node(_) => "\u{FFFC}", }) } @@ -127,38 +137,31 @@ impl ParNode { impl Debug for ParNode { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.write_str("Par ")?; - f.debug_list().entries(&self.0).finish() - } -} - -/// A child of a paragraph node. -#[derive(Hash)] -pub enum ParChild { - /// Spacing between other nodes. - Spacing(SpacingKind), - /// A run of text and how to align it in its line. - Text(TextNode), - /// Any child node and how to align it in its line. - Node(PackedNode), -} - -impl ParChild { - /// Create a text child. - pub fn text(text: impl Into<EcoString>) -> Self { - Self::Text(TextNode(text.into())) + self.0.fmt(f) } } impl Debug for ParChild { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { - Self::Spacing(kind) => kind.fmt(f), - Self::Text(text) => text.fmt(f), + Self::Text(text) => write!(f, "Text({:?})", text), + Self::Spacing(kind) => write!(f, "{:?}", kind), Self::Node(node) => node.fmt(f), } } } +impl Merge for ParChild { + fn merge(&mut self, next: &Self) -> bool { + if let (Self::Text(left), Self::Text(right)) = (self, next) { + left.push_str(right); + true + } else { + false + } + } +} + /// A paragraph break. pub struct ParbreakNode; @@ -222,20 +225,9 @@ impl<'a> ParLayouter<'a> { let mut ranges = vec![]; // Layout the children and collect them into items. - for (range, styled) in par.ranges().zip(&par.0) { - let styles = styled.map.chain(styles); - match styled.item { - ParChild::Spacing(kind) => match kind { - SpacingKind::Linear(v) => { - let resolved = v.resolve(regions.current.x); - items.push(ParItem::Absolute(resolved)); - ranges.push(range); - } - SpacingKind::Fractional(v) => { - items.push(ParItem::Fractional(v)); - ranges.push(range); - } - }, + for (range, (child, map)) in par.ranges().zip(par.0.iter()) { + let styles = map.chain(styles); + match child { ParChild::Text(_) => { // TODO: Also split by language and script. let mut cursor = range.start; @@ -249,7 +241,18 @@ impl<'a> ParLayouter<'a> { ranges.push(subrange); } } - ParChild::Node(ref node) => { + ParChild::Spacing(kind) => match *kind { + SpacingKind::Linear(v) => { + let resolved = v.resolve(regions.current.x); + items.push(ParItem::Absolute(resolved)); + ranges.push(range); + } + SpacingKind::Fractional(v) => { + items.push(ParItem::Fractional(v)); + ranges.push(range); + } + }, + ParChild::Node(node) => { let size = Size::new(regions.current.x, regions.base.y); let pod = Regions::one(size, regions.base, Spec::splat(false)); let frame = node.layout(ctx, &pod, styles).remove(0); diff --git a/src/library/spacing.rs b/src/library/spacing.rs index 0b555510..feadf7f3 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -31,6 +31,13 @@ pub enum SpacingKind { Fractional(Fractional), } +impl SpacingKind { + /// Whether this is fractional spacing. + pub fn is_fractional(self) -> bool { + matches!(self, Self::Fractional(_)) + } +} + castable! { SpacingKind, Expected: "linear or fractional", diff --git a/src/library/text.rs b/src/library/text.rs index 4aa1bdec..fb0f7e08 100644 --- a/src/library/text.rs +++ b/src/library/text.rs @@ -19,7 +19,7 @@ use crate::util::{EcoString, SliceExt}; /// A single run of text with the same style. #[derive(Hash)] -pub struct TextNode(pub EcoString); +pub struct TextNode; #[class] impl TextNode { @@ -144,12 +144,6 @@ impl TextNode { } } -impl Debug for TextNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Text({:?})", self.0) - } -} - /// Strong text, rendered in boldface. pub struct StrongNode; diff --git a/tests/ref/code/include.png b/tests/ref/code/include.png Binary files differindex f5a392e4..2d5d9ca7 100644 --- a/tests/ref/code/include.png +++ b/tests/ref/code/include.png diff --git a/tests/ref/coma.png b/tests/ref/coma.png Binary files differindex 34a6b30a..c3ea7ace 100644 --- a/tests/ref/coma.png +++ b/tests/ref/coma.png diff --git a/tests/ref/layout/pad.png b/tests/ref/layout/pad.png Binary files differindex 260b4586..c2906cef 100644 --- a/tests/ref/layout/pad.png +++ b/tests/ref/layout/pad.png diff --git a/tests/ref/layout/page-box.png b/tests/ref/layout/page-box.png Binary files differnew file mode 100644 index 00000000..c8d0320c --- /dev/null +++ b/tests/ref/layout/page-box.png diff --git a/tests/ref/layout/page-margin.png b/tests/ref/layout/page-margin.png Binary files differnew file mode 100644 index 00000000..211f881c --- /dev/null +++ b/tests/ref/layout/page-margin.png diff --git a/tests/ref/layout/page-style.png b/tests/ref/layout/page-style.png Binary files differnew file mode 100644 index 00000000..7132d20b --- /dev/null +++ b/tests/ref/layout/page-style.png diff --git a/tests/ref/layout/page.png b/tests/ref/layout/page.png Binary files differindex 20ac630f..35716e4d 100644 --- a/tests/ref/layout/page.png +++ b/tests/ref/layout/page.png diff --git a/tests/ref/layout/pagebreak.png b/tests/ref/layout/pagebreak.png Binary files differindex 454d106d..293e1b06 100644 --- a/tests/ref/layout/pagebreak.png +++ b/tests/ref/layout/pagebreak.png diff --git a/tests/ref/layout/place.png b/tests/ref/layout/place.png Binary files differindex fc97100f..7900f95f 100644 --- a/tests/ref/layout/place.png +++ b/tests/ref/layout/place.png diff --git a/tests/ref/layout/spacing.png b/tests/ref/layout/spacing.png Binary files differindex 193be6f5..7bb74ad9 100644 --- a/tests/ref/layout/spacing.png +++ b/tests/ref/layout/spacing.png diff --git a/tests/ref/markup/enums.png b/tests/ref/markup/enums.png Binary files differindex a257b0dd..3878f32c 100644 --- a/tests/ref/markup/enums.png +++ b/tests/ref/markup/enums.png diff --git a/tests/ref/markup/heading.png b/tests/ref/markup/heading.png Binary files differindex ac229180..09f3d7c7 100644 --- a/tests/ref/markup/heading.png +++ b/tests/ref/markup/heading.png diff --git a/tests/ref/markup/lists.png b/tests/ref/markup/lists.png Binary files differindex d39f3164..618e1191 100644 --- a/tests/ref/markup/lists.png +++ b/tests/ref/markup/lists.png diff --git a/tests/ref/markup/math.png b/tests/ref/markup/math.png Binary files differindex 448b2d12..77ec8d51 100644 --- a/tests/ref/markup/math.png +++ b/tests/ref/markup/math.png diff --git a/tests/ref/markup/raw.png b/tests/ref/markup/raw.png Binary files differindex ec39d3df..64402dae 100644 --- a/tests/ref/markup/raw.png +++ b/tests/ref/markup/raw.png diff --git a/tests/ref/markup/shorthands.png b/tests/ref/markup/shorthands.png Binary files differindex db27ef36..ad09967d 100644 --- a/tests/ref/markup/shorthands.png +++ b/tests/ref/markup/shorthands.png diff --git a/tests/ref/style/set-site.png b/tests/ref/style/set-site.png Binary files differindex 3188f7a7..408e44bf 100644 --- a/tests/ref/style/set-site.png +++ b/tests/ref/style/set-site.png diff --git a/tests/ref/text/bidi.png b/tests/ref/text/bidi.png Binary files differindex 7e0aab8f..68bd6b73 100644 --- a/tests/ref/text/bidi.png +++ b/tests/ref/text/bidi.png diff --git a/tests/ref/text/par.png b/tests/ref/text/par.png Binary files differindex 9ea42713..c080178e 100644 --- a/tests/ref/text/par.png +++ b/tests/ref/text/par.png diff --git a/tests/typ/coma.typ b/tests/typ/coma.typ index ef9e9f86..92d5695f 100644 --- a/tests/typ/coma.typ +++ b/tests/typ/coma.typ @@ -6,11 +6,10 @@ Sekretariat MA \ Dr. Max Mustermann \ Ola Nordmann, John Doe -#v(6mm) +#v(2mm) #align(center)[ - ==== 3. Übungsblatt Computerorientierte Mathematik II - #v(4mm) - *Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) #v(4mm) + ==== 3. Übungsblatt Computerorientierte Mathematik II #v(1mm) + *Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) #v(1mm) *Alle Antworten sind zu beweisen.* ] @@ -21,5 +20,4 @@ Die Tiefe eines Knotens _v_ ist die Länge des eindeutigen Weges von der Wurzel zu _v_, und die Höhe von _v_ ist die Länge eines längsten (absteigenden) Weges von _v_ zu einem Blatt. Die Höhe des Baumes ist die Höhe der Wurzel. -#v(6mm) #align(center, image("../res/graph.png", width: 75%)) diff --git a/tests/typ/layout/box-block.typ b/tests/typ/layout/box-block.typ index 14258c1e..c6928074 100644 --- a/tests/typ/layout/box-block.typ +++ b/tests/typ/layout/box-block.typ @@ -15,6 +15,7 @@ Apart #set page(height: 60pt) First! + #block[ But, soft! what light through yonder window breaks? It is the east, and Juliet is the sun. diff --git a/tests/typ/layout/page-box.typ b/tests/typ/layout/page-box.typ new file mode 100644 index 00000000..ed9d3e14 --- /dev/null +++ b/tests/typ/layout/page-box.typ @@ -0,0 +1,14 @@ +// Test that you can't do page related stuff in a container. + +--- +A +#box[ + B + #pagebreak() + #set page("a4") +] +C + +// No consequences from the page("A4") call here. +#pagebreak() +D diff --git a/tests/typ/layout/page-margin.typ b/tests/typ/layout/page-margin.typ new file mode 100644 index 00000000..44126d2d --- /dev/null +++ b/tests/typ/layout/page-margin.typ @@ -0,0 +1,20 @@ +// Test page margins. + +--- +// Set all margins at once. +[ + #set page(height: 20pt, margins: 5pt) + #place(top + left)[TL] + #place(bottom + right)[BR] +] + +--- +// Set individual margins. +#set page(height: 40pt) +[#set page(left: 0pt); #align(left)[Left]] +[#set page(right: 0pt); #align(right)[Right]] +[#set page(top: 0pt); #align(top)[Top]] +[#set page(bottom: 0pt); #align(bottom)[Bottom]] + +// Ensure that specific margins override general margins. +[#set page(margins: 0pt, left: 20pt); Overriden] diff --git a/tests/typ/layout/page-style.typ b/tests/typ/layout/page-style.typ new file mode 100644 index 00000000..f27551b2 --- /dev/null +++ b/tests/typ/layout/page-style.typ @@ -0,0 +1,28 @@ +// Test setting page styles. + +--- +// Empty with styles +// Should result in one conifer-colored A11 page. +#set page("a11", flipped: true, fill: conifer) + +--- +// Empty with styles and then pagebreak +// Should result in two forest-colored pages. +#set page(fill: forest) +#pagebreak() + +--- +// Empty with multiple page styles. +// Should result in a small white page. +#set page("a4") +#set page("a5") +#set page(width: 1cm, height: 1cm) + +--- +// Empty with multiple page styles. +// Should result in one eastern-colored A11 page. +#set page("a4") +#set page("a5") +#set page("a11", flipped: true, fill: eastern) +#set text("Roboto", white, smallcaps: true) +Typst diff --git a/tests/typ/layout/page.typ b/tests/typ/layout/page.typ index 35f338f4..89d0f2fb 100644 --- a/tests/typ/layout/page.typ +++ b/tests/typ/layout/page.typ @@ -1,34 +1,33 @@ -// Test configuring page sizes and margins. +// Test the page class. + +--- +// Just empty page. +// Should result in auto-sized page, just like nothing. +#page[] + +--- +// Just empty page with styles. +// Should result in one conifer-colored A11 page. +#page("a11", flipped: true, fill: conifer)[] --- // Set width and height. +// Should result in one high and one wide page. #set page(width: 80pt, height: 80pt) [#set page(width: 40pt);High] [#set page(height: 40pt);Wide] -// Set all margins at once. -[ - #set page(margins: 5pt) - #place(top + left)[TL] - #place(bottom + right)[BR] -] - -// Set individual margins. -#set page(height: 40pt) -[#set page(left: 0pt); #align(left)[Left]] -[#set page(right: 0pt); #align(right)[Right]] -[#set page(top: 0pt); #align(top)[Top]] -[#set page(bottom: 0pt); #align(bottom)[Bottom]] - -// Ensure that specific margins override general margins. -[#set page(margins: 0pt, left: 20pt); Overriden] - // Flipped predefined paper. [#set page(paper: "a11", flipped: true);Flipped A11] --- +// Test page fill. #set page(width: 80pt, height: 40pt, fill: eastern) #text(15pt, "Roboto", fill: white, smallcaps: true)[Typst] +#page(width: 40pt, fill: none, margins: auto, top: 10pt)[Hi] -#set page(width: 40pt, fill: none, margins: auto, top: 10pt) -Hi +--- +// Just page followed by pagebreak. +// Should result in one forest-colored A11 page and one auto-sized page. +#page("a11", flipped: true, fill: forest)[] +#pagebreak() diff --git a/tests/typ/layout/pagebreak.typ b/tests/typ/layout/pagebreak.typ index 81af710c..005ca244 100644 --- a/tests/typ/layout/pagebreak.typ +++ b/tests/typ/layout/pagebreak.typ @@ -1,35 +1,23 @@ // Test forced page breaks. --- -First of two +// Just a pagebreak. +// Should result in two auto-sized pages. #pagebreak() -#set page(height: 40pt) -Second of two --- -// Make sure that you can't do page related stuff in a container. -A -#box[ - B - #pagebreak() - #set page("a4") -] -C - -// No consequences from the page("A4") call here. +// Pagebreak, empty with styles and then pagebreak +// Should result in one auto-sized page and two conifer-colored A11 pages. +#pagebreak() +#set page(width: 2cm, fill: conifer) #pagebreak() -D --- -// Test a combination of pages with bodies and normal content. - +// Test a combination of pagebreaks, styled pages and pages with bodies. #set page(width: 80pt, height: 30pt) - -[#set page(width: 80pt); First] -#pagebreak() +[#set page(width: 60pt); First] #pagebreak() #pagebreak() -Fourth -#page(height: 20pt)[] -Sixth -[#set page(); Seventh] +Third +#page(height: 20pt, fill: forest)[] +Fif[#set page();th] diff --git a/tests/typ/layout/spacing.typ b/tests/typ/layout/spacing.typ index 37aa8eaa..82531efc 100644 --- a/tests/typ/layout/spacing.typ +++ b/tests/typ/layout/spacing.typ @@ -1,38 +1,27 @@ // Test the `h` and `v` functions. --- -// Ends paragraphs. -Tightly #v(0pt) packed +// Linebreak and v(0pt) are equivalent. +#box[A \ B] #box[A #v(0pt) B] // Eating up soft spacing. -Inv #h(0pt) isible +Inv#h(0pt)isible // Multiple spacings in a row. Add #h(10pt) #h(10pt) up // Relative to area. #let x = 25% - 4pt -| #h(x) | #h(x) | #h(x) | #h(x) | +|#h(x)|#h(x)|#h(x)|#h(x)| // Fractional. | #h(1fr) | #h(2fr) | #h(1fr) | --- -// Test spacing collapsing with parbreaks. -#v(0pt) -A -#v(0pt) -B -#v(0pt) - -C #parbreak() D - ---- -// Test that spacing can carry paragraph and page style properties. - -A[#set par(align: right);#h(1cm)]B -[#set page(height: 20pt);#v(1cm)] -B +// Test spacing collapsing before spacing. +#set par(align: right) +A #h(0pt) B #h(0pt) \ +A B --- // Missing spacing. diff --git a/tests/typ/layout/stack-1.typ b/tests/typ/layout/stack-1.typ index d8075e9d..eee1f4d1 100644 --- a/tests/typ/layout/stack-1.typ +++ b/tests/typ/layout/stack-1.typ @@ -31,7 +31,7 @@ --- // Test spacing. #set page(width: 50pt, margins: 0pt) -#set par(spacing: 5pt) +#set par(leading: 5pt) #let x = square(size: 10pt, fill: eastern) #stack(dir: rtl, spacing: 5pt, x, x, x) diff --git a/tests/typ/markup/enums.typ b/tests/typ/markup/enums.typ index c976deee..93a175d4 100644 --- a/tests/typ/markup/enums.typ +++ b/tests/typ/markup/enums.typ @@ -8,6 +8,7 @@ --- 1. First. 2. Second. + 1. Back to first. --- diff --git a/tests/typ/markup/lists.typ b/tests/typ/markup/lists.typ index 716fcd73..38fc2c63 100644 --- a/tests/typ/markup/lists.typ +++ b/tests/typ/markup/lists.typ @@ -14,13 +14,15 @@ paragraphs. --- - First level. + - Second level. There are multiple paragraphs. - Third level. - Still the same bullet point. + - Still level 2. + - At the top. --- diff --git a/tests/typ/style/construct.typ b/tests/typ/style/construct.typ index ab53d40f..65dcaf85 100644 --- a/tests/typ/style/construct.typ +++ b/tests/typ/style/construct.typ @@ -2,7 +2,7 @@ --- // Ensure that constructor styles aren't passed down the tree. -#set par(spacing: 2pt) +#set par(leading: 2pt) #list( body-indent: 20pt, [First], diff --git a/tests/typ/style/set-site.typ b/tests/typ/style/set-site.typ index b5464aeb..20d35f04 100644 --- a/tests/typ/style/set-site.typ +++ b/tests/typ/style/set-site.typ @@ -8,7 +8,6 @@ Hello *{x}* --- // Test that lists are affected by correct indents. -#set par(spacing: 4pt) #let fruit = [ - Apple - Orange @@ -23,7 +22,7 @@ Hello *{x}* --- // Test that that par spacing and text style are respected from // the outside, but the more specific fill is respected. -#set par(spacing: 4pt) +#set par(spacing: 0pt) #set text(style: "italic", fill: eastern) #let x = [And the forest #parbreak() lay silent!] #text(fill: forest, x) diff --git a/tests/typ/text/par.typ b/tests/typ/text/par.typ index 96a9eb6e..b0e60955 100644 --- a/tests/typ/text/par.typ +++ b/tests/typ/text/par.typ @@ -7,11 +7,11 @@ To the right! Where the sunlight peeks behind the mountain. --- // Test that explicit paragraph break respects active styles. -#set par(spacing: 7pt) +#set par(spacing: 0pt) [#set par(spacing: 100pt);First] [#set par(spacing: 100pt);Second] -#set par(spacing: 20pt) +#set par(spacing: 13.5pt) Third @@ -21,33 +21,27 @@ Hello #set par(spacing: 100pt) World -#set par(spacing: 0pt) +#set par(spacing: 0pt, leading: 0pt) You --- // Test that paragraphs break due to incompatibility has correct spacing. -A #set par(spacing: 0pt); B #parbreak() C +A #set par(spacing: 0pt, leading: 0pt); B #parbreak() C --- // Test that paragraph breaks due to block nodes have the correct spacing. +#set par(spacing: 10pt) - A -#set par(spacing: 0pt) +#set par(leading: 0pt) - B - C -#set par(spacing: 5pt) +#set par(leading: 5pt) - D - E --- -// Test that paragraph break due to incompatibility respects -// spacing defined by the two adjacent paragraphs. -#let a = [#set par(spacing: 40pt);Hello] -#let b = [#set par(spacing: 10pt);World] -{a}{b} - ---- // Test weird metrics. #set par(spacing: 100%, leading: 0pt) But, soft! what light through yonder window breaks? |
