summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/eval/collapse.rs109
-rw-r--r--src/eval/mod.rs2
-rw-r--r--src/eval/styles.rs380
-rw-r--r--src/eval/template.rs434
-rw-r--r--src/library/flow.rs178
-rw-r--r--src/library/mod.rs4
-rw-r--r--src/library/par.rs89
-rw-r--r--src/library/spacing.rs7
-rw-r--r--src/library/text.rs8
9 files changed, 663 insertions, 548 deletions
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;