Redesigned template layout

This commit is contained in:
Laurenz 2022-02-07 20:00:21 +01:00
parent 9730e785a8
commit 68503b9a07
44 changed files with 804 additions and 648 deletions

7
Cargo.lock generated
View File

@ -932,6 +932,12 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6" checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6"
[[package]]
name = "typed-arena"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae"
[[package]] [[package]]
name = "typst" name = "typst"
version = "0.1.0" version = "0.1.0"
@ -962,6 +968,7 @@ dependencies = [
"syntect", "syntect",
"tiny-skia", "tiny-skia",
"ttf-parser", "ttf-parser",
"typed-arena",
"typst-macros", "typst-macros",
"unicode-bidi", "unicode-bidi",
"unicode-segmentation", "unicode-segmentation",

View File

@ -24,6 +24,7 @@ fxhash = "0.2"
itertools = "0.10" itertools = "0.10"
once_cell = "1" once_cell = "1"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
typed-arena = "2"
# Text and font handling # Text and font handling
kurbo = "0.8" kurbo = "0.8"

109
src/eval/collapse.rs Normal file
View File

@ -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;
}

View File

@ -10,6 +10,7 @@ mod value;
mod styles; mod styles;
mod capture; mod capture;
mod class; mod class;
mod collapse;
mod func; mod func;
mod ops; mod ops;
mod scope; mod scope;
@ -18,6 +19,7 @@ mod template;
pub use array::*; pub use array::*;
pub use capture::*; pub use capture::*;
pub use class::*; pub use class::*;
pub use collapse::*;
pub use dict::*; pub use dict::*;
pub use func::*; pub use func::*;
pub use scope::*; pub use scope::*;

View File

@ -3,44 +3,10 @@ use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::sync::Arc; use std::sync::Arc;
/// An item with associated styles. use crate::library::{PageNode, ParNode};
#[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)
}
}
/// A map of style properties. /// A map of style properties.
#[derive(Default, Clone, Hash)] #[derive(Default, Clone, PartialEq, Hash)]
pub struct StyleMap(Vec<Entry>); pub struct StyleMap(Vec<Entry>);
impl StyleMap { impl StyleMap {
@ -93,7 +59,7 @@ impl StyleMap {
*outer *outer
} else { } else {
StyleChain { StyleChain {
first: Link::Map(self), link: Some(Link::Map(self)),
outer: Some(outer), outer: Some(outer),
} }
} }
@ -103,31 +69,16 @@ impl StyleMap {
/// equivalent to the style chain created by /// equivalent to the style chain created by
/// `self.chain(StyleChain::new(outer))`. /// `self.chain(StyleChain::new(outer))`.
/// ///
/// This is useful in the evaluation phase while building nodes and their /// This is useful over `chain` when you need an owned map without a
/// style maps, whereas `chain` would be used during layouting to combine /// lifetime, for example, because you want to store the style map inside a
/// immutable style maps from different levels of the hierarchy. /// packed node.
pub fn apply(&mut self, outer: &Self) { pub fn apply(&mut self, outer: &Self) {
self.0.splice(0 .. 0, outer.0.clone()); self.0.splice(0 .. 0, outer.0.clone());
} }
/// Subtract `other` from `self` in-place, keeping only styles that are in /// The highest-level interruption of the map.
/// `self` but not in `other`. pub fn interruption(&self) -> Option<Interruption> {
pub fn erase(&mut self, other: &Self) { self.0.iter().filter_map(|entry| entry.interruption()).max()
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))
} }
} }
@ -140,10 +91,13 @@ impl Debug for StyleMap {
} }
} }
impl PartialEq for StyleMap { /// Determines whether a style could interrupt some composable structure.
fn eq(&self, other: &Self) -> bool { #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
self.0.len() == other.0.len() && self.0.iter().all(|x| other.0.contains(x)) pub enum Interruption {
} /// The style forces a paragraph break.
Par,
/// The style forces a page break.
Page,
} }
/// A chain of style maps, similar to a linked list. /// A 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 /// 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 /// to the outermost map, trying to find a match and then folding it with
/// matches further up the chain. /// matches further up the chain.
#[derive(Clone, Copy, Hash)] #[derive(Default, Clone, Copy, Hash)]
pub struct StyleChain<'a> { pub struct StyleChain<'a> {
/// The first link in the chain. /// The first link of this chain.
first: Link<'a>, link: Option<Link<'a>>,
/// The remaining links in the chain. /// The remaining links in the chain.
outer: Option<&'a Self>, outer: Option<&'a Self>,
} }
@ -173,10 +127,52 @@ enum Link<'a> {
impl<'a> StyleChain<'a> { impl<'a> StyleChain<'a> {
/// Start a new style chain with a root map. /// Start a new style chain with a root map.
pub fn new(first: &'a StyleMap) -> Self { pub fn new(map: &'a StyleMap) -> Self {
Self { first: Link::Map(first), outer: None } 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. /// Get the (folded) value of a copyable style property.
/// ///
/// This is the method you should reach for first. If it doesn't work /// 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> { pub fn barred<'b>(&'b self, node: TypeId) -> StyleChain<'b> {
if self if self
.maps() .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 { StyleChain {
first: Link::Barrier(node), link: Some(Link::Barrier(node)),
outer: Some(self), outer: Some(self),
} }
} else { } else {
*self *self
} }
} }
}
impl<'a> StyleChain<'a> {
/// Iterate over all values for the given property in the chain. /// Iterate over all values for the given property in the chain.
fn values<P: Property>(self, _: P) -> impl Iterator<Item = &'a P::Value> { fn values<P: Property>(self, _: P) -> impl Iterator<Item = &'a P::Value> {
let mut depth = 0; 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. /// Iterate over the map links of the chain.
fn maps(self) -> impl Iterator<Item = &'a StyleMap> { fn maps(self) -> impl Iterator<Item = &'a StyleMap> {
self.links().filter_map(|link| match link { self.links().filter_map(|link| match link {
@ -280,6 +268,16 @@ impl<'a> StyleChain<'a> {
Link::Barrier(_) => None, 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<'_> { impl Debug for StyleChain<'_> {
@ -300,6 +298,192 @@ impl Debug for Link<'_> {
} }
} }
impl PartialEq for StyleChain<'_> {
fn eq(&self, other: &Self) -> bool {
let as_ptr = |s| s as *const _;
self.link == other.link && self.outer.map(as_ptr) == other.outer.map(as_ptr)
}
}
impl PartialEq for Link<'_> {
fn eq(&self, other: &Self) -> bool {
match (*self, *other) {
(Self::Map(a), Self::Map(b)) => std::ptr::eq(a, b),
(Self::Barrier(a), Self::Barrier(b)) => a == b,
_ => false,
}
}
}
/// A sequence of items with associated styles.
#[derive(Hash)]
pub struct StyleVec<T> {
items: Vec<T>,
maps: Vec<(StyleMap, usize)>,
}
impl<T> StyleVec<T> {
/// Whether there are any items in the sequence.
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
/// Iterate over the contained items.
pub fn items(&self) -> std::slice::Iter<'_, T> {
self.items.iter()
}
/// Iterate over the contained items and associated style maps.
pub fn iter(&self) -> impl Iterator<Item = (&T, &StyleMap)> + '_ {
let styles = self
.maps
.iter()
.flat_map(|(map, count)| std::iter::repeat(map).take(*count));
self.items().zip(styles)
}
}
impl<T> Default for StyleVec<T> {
fn default() -> Self {
Self { items: vec![], maps: vec![] }
}
}
impl<T: Debug> Debug for StyleVec<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_list()
.entries(self.iter().map(|(item, map)| {
crate::util::debug(|f| {
map.fmt(f)?;
item.fmt(f)
})
}))
.finish()
}
}
/// Assists in the construction of a [`StyleVec`].
pub struct StyleVecBuilder<'a, T> {
items: Vec<T>,
chains: Vec<(StyleChain<'a>, usize)>,
}
impl<'a, T> StyleVecBuilder<'a, T> {
/// Create a new style-vec builder.
pub fn new() -> Self {
Self { items: vec![], chains: vec![] }
}
/// Push a new item into the style vector.
pub fn push(&mut self, item: T, styles: StyleChain<'a>) {
self.items.push(item);
if let Some((prev, count)) = self.chains.last_mut() {
if *prev == styles {
*count += 1;
return;
}
}
self.chains.push((styles, 1));
}
/// Access the last item mutably and its chain by value.
pub fn last_mut(&mut self) -> Option<(&mut T, StyleChain<'a>)> {
let item = self.items.last_mut()?;
let chain = self.chains.last()?.0;
Some((item, chain))
}
/// Finish building, returning a pair of two things:
/// - a style vector of items with the non-shared styles
/// - a shared prefix chain of styles that apply to all items
pub fn finish(self) -> (StyleVec<T>, StyleChain<'a>) {
let mut iter = self.chains.iter();
let mut trunk = match iter.next() {
Some(&(chain, _)) => chain,
None => return Default::default(),
};
let mut shared = trunk.len();
for &(mut chain, _) in iter {
let len = chain.len();
if len < shared {
for _ in 0 .. shared - len {
trunk.pop();
}
shared = len;
} else if len > shared {
for _ in 0 .. len - shared {
chain.pop();
}
}
while shared > 0 && chain != trunk {
trunk.pop();
chain.pop();
shared -= 1;
}
}
let maps = self
.chains
.into_iter()
.map(|(chain, count)| (chain.suffix(shared), count))
.collect();
(StyleVec { items: self.items, maps }, trunk)
}
}
impl<'a, T> Default for StyleVecBuilder<'a, T> {
fn default() -> Self {
Self::new()
}
}
/// Style property keys.
///
/// This trait is not intended to be implemented manually, but rather through
/// the `#[class]` proc-macro.
pub trait Property: Sync + Send + 'static {
/// The type of value that is returned when getting this property from a
/// style map. For example, this could be [`Length`](crate::geom::Length)
/// for a `WIDTH` property.
type Value: Debug + Clone + PartialEq + Hash + Sync + Send + 'static;
/// The name of the property, used for debug printing.
const NAME: &'static str;
/// Whether the property needs folding.
const FOLDING: bool = false;
/// The type id of the node this property belongs to.
fn node_id() -> TypeId;
/// The default value of the property.
fn default() -> Self::Value;
/// A static reference to the default value of the property.
///
/// This is automatically implemented through lazy-initialization in the
/// `#[class]` macro. This way, expensive defaults don't need to be
/// recreated all the time.
fn default_ref() -> &'static Self::Value;
/// Fold the property with an outer value.
///
/// For example, this would fold a relative font size with an outer
/// absolute font size.
#[allow(unused_variables)]
fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value {
inner
}
}
/// Marker trait that indicates that a property doesn't need folding.
pub trait Nonfolding {}
/// An entry for a single style property. /// An entry for a single style property.
#[derive(Clone)] #[derive(Clone)]
struct Entry { struct Entry {
@ -323,13 +507,23 @@ impl Entry {
self.pair.node_id() == TypeId::of::<T>() self.pair.node_id() == TypeId::of::<T>()
} }
fn is_of_same(&self, node: TypeId) -> bool { fn is_of_id(&self, node: TypeId) -> bool {
self.pair.node_id() == node self.pair.node_id() == node
} }
fn downcast<P: Property>(&self) -> Option<&P::Value> { fn downcast<P: Property>(&self) -> Option<&P::Value> {
self.pair.as_any().downcast_ref() 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 { impl Debug for Entry {
@ -356,48 +550,6 @@ impl Hash for Entry {
} }
} }
/// Style property keys.
///
/// This trait is not intended to be implemented manually, but rather through
/// the `#[properties]` proc-macro.
pub trait Property: Sync + Send + 'static {
/// The type of value that is returned when getting this property from a
/// style map. For example, this could be [`Length`](crate::geom::Length)
/// for a `WIDTH` property.
type Value: Debug + Clone + PartialEq + Hash + Sync + Send + 'static;
/// The name of the property, used for debug printing.
const NAME: &'static str;
/// Whether the property needs folding.
const FOLDING: bool = false;
/// The type id of the node this property belongs to.
fn node_id() -> TypeId;
/// The default value of the property.
fn default() -> Self::Value;
/// A static reference to the default value of the property.
///
/// This is automatically implemented through lazy-initialization in the
/// `#[properties]` macro. This way, expensive defaults don't need to be
/// recreated all the time.
fn default_ref() -> &'static Self::Value;
/// Fold the property with an outer value.
///
/// For example, this would fold a relative font size with an outer
/// absolute font size.
#[allow(unused_variables)]
fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value {
inner
}
}
/// Marker trait that indicates that a property doesn't need folding.
pub trait Nonfolding {}
/// This trait is implemented for pairs of zero-sized property keys and their /// 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 /// 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 /// of the implementing type so that we can use it in the methods (it must be a

View File

@ -1,11 +1,12 @@
use std::convert::TryFrom;
use std::fmt::Debug; use std::fmt::Debug;
use std::hash::Hash; use std::hash::Hash;
use std::iter::Sum; use std::iter::Sum;
use std::mem; use std::mem;
use std::ops::{Add, AddAssign}; 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::diag::StrResult;
use crate::layout::{Layout, PackedNode}; use crate::layout::{Layout, PackedNode};
use crate::library::prelude::*; use crate::library::prelude::*;
@ -50,16 +51,16 @@ pub enum Template {
Horizontal(SpacingKind), Horizontal(SpacingKind),
/// Plain text. /// Plain text.
Text(EcoString), Text(EcoString),
/// An inline node. /// An inline-level node.
Inline(PackedNode), Inline(PackedNode),
/// A paragraph break. /// A paragraph break.
Parbreak, Parbreak,
/// Vertical spacing.
Vertical(SpacingKind),
/// A block node.
Block(PackedNode),
/// A column break. /// A column break.
Colbreak, Colbreak,
/// Vertical spacing.
Vertical(SpacingKind),
/// A block-level node.
Block(PackedNode),
/// A page break. /// A page break.
Pagebreak, Pagebreak,
/// A page node. /// A page node.
@ -95,12 +96,11 @@ impl Template {
/// Layout this template into a collection of pages. /// Layout this template into a collection of pages.
pub fn layout(&self, ctx: &mut Context) -> Vec<Arc<Frame>> { pub fn layout(&self, ctx: &mut Context) -> Vec<Arc<Frame>> {
let (mut ctx, styles) = LayoutContext::new(ctx); let (mut ctx, styles) = LayoutContext::new(ctx);
let mut packer = Packer::new(true); let (pages, shared) = Builder::build_pages(self);
packer.walk(self.clone(), StyleMap::new()); let styles = shared.chain(&styles);
packer pages
.into_root()
.iter() .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() .collect()
} }
@ -159,13 +159,13 @@ impl Debug for Template {
f.write_str(")") f.write_str(")")
} }
Self::Parbreak => f.pad("Parbreak"), Self::Parbreak => f.pad("Parbreak"),
Self::Colbreak => f.pad("Colbreak"),
Self::Vertical(kind) => write!(f, "Vertical({kind:?})"), Self::Vertical(kind) => write!(f, "Vertical({kind:?})"),
Self::Block(node) => { Self::Block(node) => {
f.write_str("Block(")?; f.write_str("Block(")?;
node.fmt(f)?; node.fmt(f)?;
f.write_str(")") f.write_str(")")
} }
Self::Colbreak => f.pad("Colbreak"),
Self::Pagebreak => f.pad("Pagebreak"), Self::Pagebreak => f.pad("Pagebreak"),
Self::Page(page) => page.fmt(f), Self::Page(page) => page.fmt(f),
Self::Styled(sub, map) => { Self::Styled(sub, map) => {
@ -220,338 +220,172 @@ impl Layout for Template {
regions: &Regions, regions: &Regions,
styles: StyleChain, styles: StyleChain,
) -> Vec<Constrained<Arc<Frame>>> { ) -> Vec<Constrained<Arc<Frame>>> {
let mut packer = Packer::new(false); let (flow, shared) = Builder::build_flow(self);
packer.walk(self.clone(), StyleMap::new()); flow.layout(ctx, regions, shared.chain(&styles))
packer.into_block().layout(ctx, regions, styles)
} }
fn pack(self) -> PackedNode { fn pack(self) -> PackedNode {
if let Template::Block(packed) = self { match self {
packed Template::Block(node) => node,
} else { other => PackedNode::new(other),
PackedNode::new(self)
} }
} }
} }
/// Packs a [`Template`] into a flow or root node. /// Builds a flow or page nodes from a template.
struct Packer { struct Builder<'a> {
/// Whether this packer produces a root node. /// An arena where intermediate style chains are stored.
top: bool, arena: &'a Arena<StyleChain<'a>>,
/// The accumulated page nodes. /// The already built page runs.
pages: Vec<Styled<PageNode>>, pages: Option<StyleVecBuilder<'a, PageNode>>,
/// The accumulated flow children. /// The currently built flow.
flow: Builder<Styled<FlowChild>>, flow: CollapsingBuilder<'a, FlowChild>,
/// The accumulated paragraph children. /// The currently built paragraph.
par: Builder<Styled<ParChild>>, par: CollapsingBuilder<'a, ParChild>,
/// Whether to keep the next page even if it is empty.
keep_next: bool,
} }
impl Packer { impl<'a> Builder<'a> {
/// Start a new template-packing session. /// Build page runs from a template.
fn new(top: bool) -> Self { 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())
}
/// 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())
}
/// Prepare the builder.
fn prepare(arena: &'a Arena<StyleChain<'a>>, top: bool) -> Self {
Self { Self {
top, arena,
pages: vec![], pages: top.then(|| StyleVecBuilder::new()),
flow: Builder::default(), flow: CollapsingBuilder::new(),
par: Builder::default(), par: CollapsingBuilder::new(),
keep_next: true,
} }
} }
/// Finish up and return the resulting flow. /// Process a template.
fn into_block(mut self) -> PackedNode { fn process(&mut self, template: &'a Template, styles: StyleChain<'a>) {
self.parbreak(None, false);
FlowNode(self.flow.children).pack()
}
/// Finish up and return the resulting root node.
fn into_root(mut self) -> Vec<Styled<PageNode>> {
self.pagebreak();
self.pages
}
/// Consider a template with the given styles.
fn walk(&mut self, template: Template, styles: StyleMap) {
match template { match template {
Template::Space => { Template::Space => {
// A text space is "soft", meaning that it can be eaten up by self.par.weak(ParChild::Text(' '.into()), styles);
// adjacent line breaks or explicit spacings.
self.par.last.soft(Styled::new(ParChild::text(' '), styles), false);
} }
Template::Linebreak => { Template::Linebreak => {
// A line break eats up surrounding text spaces. self.par.destructive(ParChild::Text('\n'.into()), styles);
self.par.last.hard();
self.push_inline(Styled::new(ParChild::text('\n'), styles));
self.par.last.hard();
}
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::Text(text) => {
self.push_inline(Styled::new(ParChild::text(text), styles));
} }
Template::Horizontal(kind) => { Template::Horizontal(kind) => {
// Just like a line break, explicit horizontal spacing eats up let child = ParChild::Spacing(*kind);
// surrounding text spaces. if kind.is_fractional() {
self.par.last.hard(); self.par.destructive(child, styles);
self.push_inline(Styled::new(ParChild::Spacing(kind), styles)); } else {
self.par.last.hard(); self.par.ignorant(child, styles);
}
}
Template::Text(text) => {
self.par.supportive(ParChild::Text(text.clone()), styles);
}
Template::Inline(node) => {
self.par.supportive(ParChild::Node(node.clone()), styles);
}
Template::Parbreak => {
self.finish_par();
self.flow.weak(FlowChild::Parbreak, styles);
}
Template::Colbreak => {
self.finish_par();
self.flow.destructive(FlowChild::Colbreak, styles);
} }
Template::Vertical(kind) => { Template::Vertical(kind) => {
// Explicit vertical spacing ends the current paragraph and then self.finish_par();
// discards the paragraph break. let child = FlowChild::Spacing(*kind);
self.parbreak(None, false); if kind.is_fractional() {
self.make_flow_compatible(&styles); self.flow.destructive(child, styles);
self.flow.children.push(Styled::new(FlowChild::Spacing(kind), styles)); } else {
self.flow.last.hard(); self.flow.ignorant(child, styles);
}
} }
Template::Inline(inline) => { Template::Block(node) => {
self.push_inline(Styled::new(ParChild::Node(inline), styles)); self.finish_par();
let child = FlowChild::Node(node.clone());
if node.is::<PlaceNode>() {
self.flow.ignorant(child, styles);
} else {
self.flow.supportive(child, styles);
}
} }
Template::Block(block) => { Template::Pagebreak => {
self.push_block(Styled::new(block, styles)); self.finish_page(true, true, styles);
} }
Template::Page(page) => { Template::Page(page) => {
if self.top { self.finish_page(false, false, styles);
self.pagebreak(); if let Some(pages) = &mut self.pages {
self.pages.push(Styled::new(page, styles)); pages.push(page.clone(), styles);
} else {
self.push_block(Styled::new(page.0, styles));
} }
} }
Template::Styled(template, mut map) => { Template::Styled(sub, map) => {
map.apply(&styles); let interruption = map.interruption();
self.walk(*template, map); match interruption {
Some(Interruption::Page) => self.finish_page(false, true, styles),
Some(Interruption::Par) => self.finish_par(),
None => {}
}
let outer = self.arena.alloc(styles);
let styles = map.chain(outer);
self.process(sub, styles);
match interruption {
Some(Interruption::Page) => self.finish_page(true, false, styles),
Some(Interruption::Par) => self.finish_par(),
None => {}
}
} }
Template::Sequence(seq) => { Template::Sequence(seq) => {
// For a list of templates, we apply the list's styles to each for sub in seq {
// templates individually. self.process(sub, styles);
for item in seq {
self.walk(item, styles.clone());
} }
} }
} }
} }
/// Insert an inline-level element into the current paragraph. /// Finish the currently built paragraph.
fn push_inline(&mut self, child: Styled<ParChild>) { fn finish_par(&mut self) {
// The child's map must be both compatible with the current page and the let (par, shared) = mem::take(&mut self.par).finish();
// current paragraph. if !par.is_empty() {
self.make_flow_compatible(&child.map); let node = ParNode(par).pack();
self.make_par_compatible(&child.map); self.flow.supportive(FlowChild::Node(node), shared);
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. /// Finish the currently built page run.
fn push_coalescing(&mut self, child: Styled<ParChild>) { fn finish_page(&mut self, keep_last: bool, keep_next: bool, styles: StyleChain<'a>) {
if let ParChild::Text(right) = &child.item { self.finish_par();
if let Some(Styled { item: ParChild::Text(left), map }) = if let Some(pages) = &mut self.pages {
self.par.children.last_mut() let (flow, shared) = mem::take(&mut self.flow).finish();
{ if !flow.is_empty() || (keep_last && self.keep_next) {
if child.map.compatible::<TextNode>(map) { let styles = if flow.is_empty() { styles } else { shared };
left.0.push_str(&right.0); let node = PageNode(FlowNode(flow).pack());
return; pages.push(node, styles);
}
} }
} }
self.keep_next = keep_next;
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();
}
}
/// 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;
}
}
// 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);
}
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;
}
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);
}
}
/// 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;
} }
} }

View File

@ -1,16 +1,27 @@
//! A flow of paragraphs and other block-level nodes. //! A flow of paragraphs and other block-level nodes.
use std::fmt::{self, Debug, Formatter};
use super::prelude::*; use super::prelude::*;
use super::{AlignNode, ParNode, PlaceNode, SpacingKind, TextNode}; 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 /// This node is reponsible for layouting both the top-level content flow and
/// the contents of boxes. /// the contents of boxes.
#[derive(Hash)] #[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 { impl Layout for FlowNode {
fn layout( fn layout(
@ -19,45 +30,58 @@ impl Layout for FlowNode {
regions: &Regions, regions: &Regions,
styles: StyleChain, styles: StyleChain,
) -> Vec<Constrained<Arc<Frame>>> { ) -> 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 { impl Debug for FlowNode {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("Flow ")?; 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 { impl Debug for FlowChild {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
Self::Break => f.pad("Break"), Self::Parbreak => f.pad("Parbreak"),
Self::Skip => f.pad("Skip"), Self::Colbreak => f.pad("Colbreak"),
Self::Spacing(kind) => kind.fmt(f), Self::Spacing(kind) => write!(f, "{:?}", kind),
Self::Node(node) => node.fmt(f), Self::Node(node) => node.fmt(f),
} }
} }
} }
/// Performs flow layout. /// Performs flow layout.
struct FlowLayouter<'a> { pub struct FlowLayouter {
/// The children of the flow.
children: &'a [Styled<FlowChild>],
/// The regions to layout children into. /// The regions to layout children into.
regions: Regions, regions: Regions,
/// Whether the flow should expand to fill the region. /// Whether the flow should expand to fill the region.
@ -69,6 +93,8 @@ struct FlowLayouter<'a> {
used: Size, used: Size,
/// The sum of fractional ratios in the current region. /// The sum of fractional ratios in the current region.
fr: Fractional, fr: Fractional,
/// Whether to add leading before the next node.
leading: bool,
/// Spacing and layouted nodes. /// Spacing and layouted nodes.
items: Vec<FlowItem>, items: Vec<FlowItem>,
/// Finished frames for previous regions. /// Finished frames for previous regions.
@ -87,98 +113,64 @@ enum FlowItem {
Placed(Arc<Frame>), Placed(Arc<Frame>),
} }
impl<'a> FlowLayouter<'a> { impl FlowLayouter {
/// Create a new flow layouter. /// 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 expand = regions.expand;
let full = regions.current; let full = regions.current;
// Disable vertical expansion for children. // Disable vertical expansion for children.
let mut regions = regions.clone();
regions.expand.y = false; regions.expand.y = false;
Self { Self {
children: &flow.0,
regions, regions,
expand, expand,
full, full,
used: Size::zero(), used: Size::zero(),
fr: Fractional::zero(), fr: Fractional::zero(),
leading: false,
items: vec![], items: vec![],
finished: 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. /// Layout spacing.
fn layout_spacing(&mut self, spacing: SpacingKind) { pub fn layout_spacing(&mut self, spacing: SpacingKind) {
match spacing { 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) => { SpacingKind::Fractional(v) => {
self.items.push(FlowItem::Fractional(v)); self.items.push(FlowItem::Fractional(v));
self.fr += 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. /// Layout a node.
fn layout_node( pub fn layout_node(
&mut self, &mut self,
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
node: &PackedNode, node: &PackedNode,
styles: StyleChain, 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 // Placed nodes that are out of flow produce placed items which aren't
// aligned later. // aligned later.
let mut is_placed = false;
if let Some(placed) = node.downcast::<PlaceNode>() { if let Some(placed) = node.downcast::<PlaceNode>() {
is_placed = true;
if placed.out_of_flow() { if placed.out_of_flow() {
let frame = node.layout(ctx, &self.regions, styles).remove(0); let frame = node.layout(ctx, &self.regions, styles).remove(0);
self.items.push(FlowItem::Placed(frame.item)); 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. // How to align the node.
let aligns = Spec::new( let aligns = Spec::new(
// For non-expanding paragraphs it is crucial that we align the // For non-expanding paragraphs it is crucial that we align the
@ -211,10 +210,12 @@ impl<'a> FlowLayouter<'a> {
self.finish_region(); self.finish_region();
} }
} }
self.leading = !is_placed;
} }
/// Finish the frame for one region. /// 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 // Determine the size of the flow in this region dependening on whether
// the region expands. // the region expands.
let mut size = self.expand.select(self.full, self.used); let mut size = self.expand.select(self.full, self.used);
@ -263,6 +264,19 @@ impl<'a> FlowLayouter<'a> {
self.full = self.regions.current; self.full = self.regions.current;
self.used = Size::zero(); self.used = Size::zero();
self.fr = Fractional::zero(); self.fr = Fractional::zero();
self.leading = false;
self.finished.push(output.constrain(cts)); 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
}
} }

View File

@ -68,8 +68,8 @@ prelude! {
pub use crate::diag::{At, TypResult}; pub use crate::diag::{At, TypResult};
pub use crate::eval::{ pub use crate::eval::{
Args, Construct, EvalContext, Template, Property, Scope, Set, Smart, StyleChain, Args, Construct, EvalContext, Merge, Property, Scope, Set, Smart, StyleChain,
StyleMap, Styled, Value, StyleMap, StyleVec, Template, Value,
}; };
pub use crate::frame::*; pub use crate::frame::*;
pub use crate::geom::*; pub use crate::geom::*;

View File

@ -1,6 +1,5 @@
//! Paragraph layout. //! Paragraph layout.
use std::fmt::{self, Debug, Formatter};
use std::sync::Arc; use std::sync::Arc;
use itertools::Either; use itertools::Either;
@ -11,9 +10,20 @@ use super::prelude::*;
use super::{shape, ShapedText, SpacingKind, TextNode}; use super::{shape, ShapedText, SpacingKind, TextNode};
use crate::util::{ArcExt, EcoString, RangeExt, SliceExt}; 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)] #[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] #[class]
impl ParNode { impl ParNode {
@ -23,8 +33,8 @@ impl ParNode {
pub const ALIGN: Align = Align::Left; pub const ALIGN: Align = Align::Left;
/// The spacing between lines (dependent on scaled font size). /// The spacing between lines (dependent on scaled font size).
pub const LEADING: Linear = Relative::new(0.65).into(); pub const LEADING: Linear = Relative::new(0.65).into();
/// The spacing between paragraphs (dependent on scaled font size). /// The extra spacing between paragraphs (dependent on scaled font size).
pub const SPACING: Linear = Relative::new(1.2).into(); pub const SPACING: Linear = Relative::new(0.55).into();
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> { fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
// The paragraph constructor is special: It doesn't create a paragraph // The paragraph constructor is special: It doesn't create a paragraph
@ -116,9 +126,9 @@ impl ParNode {
/// The string representation of each child. /// The string representation of each child.
fn strings(&self) -> impl Iterator<Item = &str> { 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::Spacing(_) => " ",
ParChild::Text(text) => &text.0,
ParChild::Node(_) => "\u{FFFC}", ParChild::Node(_) => "\u{FFFC}",
}) })
} }
@ -127,38 +137,31 @@ impl ParNode {
impl Debug for ParNode { impl Debug for ParNode {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("Par ")?; f.write_str("Par ")?;
f.debug_list().entries(&self.0).finish() self.0.fmt(f)
}
}
/// 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()))
} }
} }
impl Debug for ParChild { impl Debug for ParChild {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
Self::Spacing(kind) => kind.fmt(f), Self::Text(text) => write!(f, "Text({:?})", text),
Self::Text(text) => text.fmt(f), Self::Spacing(kind) => write!(f, "{:?}", kind),
Self::Node(node) => node.fmt(f), 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. /// A paragraph break.
pub struct ParbreakNode; pub struct ParbreakNode;
@ -222,20 +225,9 @@ impl<'a> ParLayouter<'a> {
let mut ranges = vec![]; let mut ranges = vec![];
// Layout the children and collect them into items. // Layout the children and collect them into items.
for (range, styled) in par.ranges().zip(&par.0) { for (range, (child, map)) in par.ranges().zip(par.0.iter()) {
let styles = styled.map.chain(styles); let styles = map.chain(styles);
match styled.item { match child {
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::Text(_) => { ParChild::Text(_) => {
// TODO: Also split by language and script. // TODO: Also split by language and script.
let mut cursor = range.start; let mut cursor = range.start;
@ -249,7 +241,18 @@ impl<'a> ParLayouter<'a> {
ranges.push(subrange); 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 size = Size::new(regions.current.x, regions.base.y);
let pod = Regions::one(size, regions.base, Spec::splat(false)); let pod = Regions::one(size, regions.base, Spec::splat(false));
let frame = node.layout(ctx, &pod, styles).remove(0); let frame = node.layout(ctx, &pod, styles).remove(0);

View File

@ -31,6 +31,13 @@ pub enum SpacingKind {
Fractional(Fractional), Fractional(Fractional),
} }
impl SpacingKind {
/// Whether this is fractional spacing.
pub fn is_fractional(self) -> bool {
matches!(self, Self::Fractional(_))
}
}
castable! { castable! {
SpacingKind, SpacingKind,
Expected: "linear or fractional", Expected: "linear or fractional",

View File

@ -19,7 +19,7 @@ use crate::util::{EcoString, SliceExt};
/// A single run of text with the same style. /// A single run of text with the same style.
#[derive(Hash)] #[derive(Hash)]
pub struct TextNode(pub EcoString); pub struct TextNode;
#[class] #[class]
impl TextNode { 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. /// Strong text, rendered in boldface.
pub struct StrongNode; pub struct StrongNode;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 756 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -6,11 +6,10 @@ Sekretariat MA \
Dr. Max Mustermann \ Dr. Max Mustermann \
Ola Nordmann, John Doe Ola Nordmann, John Doe
#v(6mm) #v(2mm)
#align(center)[ #align(center)[
==== 3. Übungsblatt Computerorientierte Mathematik II ==== 3. Übungsblatt Computerorientierte Mathematik II #v(1mm)
#v(4mm) *Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) #v(1mm)
*Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) #v(4mm)
*Alle Antworten sind zu beweisen.* *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 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. 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%)) #align(center, image("../res/graph.png", width: 75%))

View File

@ -15,6 +15,7 @@ Apart
#set page(height: 60pt) #set page(height: 60pt)
First! First!
#block[ #block[
But, soft! what light through yonder window breaks? It is the east, and Juliet But, soft! what light through yonder window breaks? It is the east, and Juliet
is the sun. is the sun.

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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. // Set width and height.
// Should result in one high and one wide page.
#set page(width: 80pt, height: 80pt) #set page(width: 80pt, height: 80pt)
[#set page(width: 40pt);High] [#set page(width: 40pt);High]
[#set page(height: 40pt);Wide] [#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. // Flipped predefined paper.
[#set page(paper: "a11", flipped: true);Flipped A11] [#set page(paper: "a11", flipped: true);Flipped A11]
--- ---
// Test page fill.
#set page(width: 80pt, height: 40pt, fill: eastern) #set page(width: 80pt, height: 40pt, fill: eastern)
#text(15pt, "Roboto", fill: white, smallcaps: true)[Typst] #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()

View File

@ -1,35 +1,23 @@
// Test forced page breaks. // Test forced page breaks.
--- ---
First of two // Just a pagebreak.
// Should result in two auto-sized pages.
#pagebreak() #pagebreak()
#set page(height: 40pt)
Second of two
--- ---
// Make sure that you can't do page related stuff in a container. // Pagebreak, empty with styles and then pagebreak
A // Should result in one auto-sized page and two conifer-colored A11 pages.
#box[ #pagebreak()
B #set page(width: 2cm, fill: conifer)
#pagebreak()
#set page("a4")
]
C
// No consequences from the page("A4") call here.
#pagebreak() #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, height: 30pt)
[#set page(width: 60pt); First]
[#set page(width: 80pt); First]
#pagebreak() #pagebreak()
#pagebreak() #pagebreak()
#pagebreak() Third
Fourth #page(height: 20pt, fill: forest)[]
#page(height: 20pt)[] Fif[#set page();th]
Sixth
[#set page(); Seventh]

View File

@ -1,38 +1,27 @@
// Test the `h` and `v` functions. // Test the `h` and `v` functions.
--- ---
// Ends paragraphs. // Linebreak and v(0pt) are equivalent.
Tightly #v(0pt) packed #box[A \ B] #box[A #v(0pt) B]
// Eating up soft spacing. // Eating up soft spacing.
Inv #h(0pt) isible Inv#h(0pt)isible
// Multiple spacings in a row. // Multiple spacings in a row.
Add #h(10pt) #h(10pt) up Add #h(10pt) #h(10pt) up
// Relative to area. // Relative to area.
#let x = 25% - 4pt #let x = 25% - 4pt
| #h(x) | #h(x) | #h(x) | #h(x) | |#h(x)|#h(x)|#h(x)|#h(x)|
// Fractional. // Fractional.
| #h(1fr) | #h(2fr) | #h(1fr) | | #h(1fr) | #h(2fr) | #h(1fr) |
--- ---
// Test spacing collapsing with parbreaks. // Test spacing collapsing before spacing.
#v(0pt) #set par(align: right)
A A #h(0pt) B #h(0pt) \
#v(0pt) A B
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
--- ---
// Missing spacing. // Missing spacing.

View File

@ -31,7 +31,7 @@
--- ---
// Test spacing. // Test spacing.
#set page(width: 50pt, margins: 0pt) #set page(width: 50pt, margins: 0pt)
#set par(spacing: 5pt) #set par(leading: 5pt)
#let x = square(size: 10pt, fill: eastern) #let x = square(size: 10pt, fill: eastern)
#stack(dir: rtl, spacing: 5pt, x, x, x) #stack(dir: rtl, spacing: 5pt, x, x, x)

View File

@ -8,6 +8,7 @@
--- ---
1. First. 1. First.
2. Second. 2. Second.
1. Back to first. 1. Back to first.
--- ---

View File

@ -14,13 +14,15 @@ paragraphs.
--- ---
- First level. - First level.
- Second level. - Second level.
There are multiple paragraphs. There are multiple paragraphs.
- Third level. - Third level.
Still the same bullet point. Still the same bullet point.
- Still level 2. - Still level 2.
- At the top. - At the top.
--- ---

View File

@ -2,7 +2,7 @@
--- ---
// Ensure that constructor styles aren't passed down the tree. // Ensure that constructor styles aren't passed down the tree.
#set par(spacing: 2pt) #set par(leading: 2pt)
#list( #list(
body-indent: 20pt, body-indent: 20pt,
[First], [First],

View File

@ -8,7 +8,6 @@ Hello *{x}*
--- ---
// Test that lists are affected by correct indents. // Test that lists are affected by correct indents.
#set par(spacing: 4pt)
#let fruit = [ #let fruit = [
- Apple - Apple
- Orange - Orange
@ -23,7 +22,7 @@ Hello *{x}*
--- ---
// Test that that par spacing and text style are respected from // Test that that par spacing and text style are respected from
// the outside, but the more specific fill is respected. // the outside, but the more specific fill is respected.
#set par(spacing: 4pt) #set par(spacing: 0pt)
#set text(style: "italic", fill: eastern) #set text(style: "italic", fill: eastern)
#let x = [And the forest #parbreak() lay silent!] #let x = [And the forest #parbreak() lay silent!]
#text(fill: forest, x) #text(fill: forest, x)

View File

@ -7,11 +7,11 @@ To the right! Where the sunlight peeks behind the mountain.
--- ---
// Test that explicit paragraph break respects active styles. // 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);First]
[#set par(spacing: 100pt);Second] [#set par(spacing: 100pt);Second]
#set par(spacing: 20pt) #set par(spacing: 13.5pt)
Third Third
@ -21,32 +21,26 @@ Hello
#set par(spacing: 100pt) #set par(spacing: 100pt)
World World
#set par(spacing: 0pt) #set par(spacing: 0pt, leading: 0pt)
You You
--- ---
// Test that paragraphs break due to incompatibility has correct spacing. // 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. // Test that paragraph breaks due to block nodes have the correct spacing.
#set par(spacing: 10pt)
- A - A
#set par(spacing: 0pt) #set par(leading: 0pt)
- B - B
- C - C
#set par(spacing: 5pt) #set par(leading: 5pt)
- D - D
- E - 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. // Test weird metrics.
#set par(spacing: 100%, leading: 0pt) #set par(spacing: 100%, leading: 0pt)