Redesigned template layout
7
Cargo.lock
generated
@ -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",
|
||||||
|
@ -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
@ -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;
|
||||||
|
}
|
@ -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::*;
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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::*;
|
||||||
|
@ -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);
|
||||||
|
@ -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",
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 85 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
BIN
tests/ref/layout/page-box.png
Normal file
After Width: | Height: | Size: 756 B |
BIN
tests/ref/layout/page-margin.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
tests/ref/layout/page-style.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |
@ -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%))
|
||||||
|
@ -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.
|
||||||
|
14
tests/typ/layout/page-box.typ
Normal 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
|
20
tests/typ/layout/page-margin.typ
Normal 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]
|
28
tests/typ/layout/page-style.typ
Normal 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
|
@ -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()
|
||||||
|
@ -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]
|
|
||||||
|
@ -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.
|
||||||
|
@ -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)
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
---
|
---
|
||||||
1. First.
|
1. First.
|
||||||
2. Second.
|
2. Second.
|
||||||
|
|
||||||
1. Back to first.
|
1. Back to first.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -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],
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|