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"
|
||||
checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6"
|
||||
|
||||
[[package]]
|
||||
name = "typed-arena"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae"
|
||||
|
||||
[[package]]
|
||||
name = "typst"
|
||||
version = "0.1.0"
|
||||
@ -962,6 +968,7 @@ dependencies = [
|
||||
"syntect",
|
||||
"tiny-skia",
|
||||
"ttf-parser",
|
||||
"typed-arena",
|
||||
"typst-macros",
|
||||
"unicode-bidi",
|
||||
"unicode-segmentation",
|
||||
|
@ -24,6 +24,7 @@ fxhash = "0.2"
|
||||
itertools = "0.10"
|
||||
once_cell = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
typed-arena = "2"
|
||||
|
||||
# Text and font handling
|
||||
kurbo = "0.8"
|
||||
|
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 capture;
|
||||
mod class;
|
||||
mod collapse;
|
||||
mod func;
|
||||
mod ops;
|
||||
mod scope;
|
||||
@ -18,6 +19,7 @@ mod template;
|
||||
pub use array::*;
|
||||
pub use capture::*;
|
||||
pub use class::*;
|
||||
pub use collapse::*;
|
||||
pub use dict::*;
|
||||
pub use func::*;
|
||||
pub use scope::*;
|
||||
|
@ -3,44 +3,10 @@ use std::fmt::{self, Debug, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// An item with associated styles.
|
||||
#[derive(PartialEq, Clone, Hash)]
|
||||
pub struct Styled<T> {
|
||||
/// The item to apply styles to.
|
||||
pub item: T,
|
||||
/// The associated style map.
|
||||
pub map: StyleMap,
|
||||
}
|
||||
|
||||
impl<T> Styled<T> {
|
||||
/// Create a new instance from an item and a style map.
|
||||
pub fn new(item: T, map: StyleMap) -> Self {
|
||||
Self { item, map }
|
||||
}
|
||||
|
||||
/// Create a new instance with empty style map.
|
||||
pub fn bare(item: T) -> Self {
|
||||
Self { item, map: StyleMap::new() }
|
||||
}
|
||||
|
||||
/// Map the item with `f`.
|
||||
pub fn map<F, U>(self, f: F) -> Styled<U>
|
||||
where
|
||||
F: FnOnce(T) -> U,
|
||||
{
|
||||
Styled { item: f(self.item), map: self.map }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for Styled<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.map.fmt(f)?;
|
||||
self.item.fmt(f)
|
||||
}
|
||||
}
|
||||
use crate::library::{PageNode, ParNode};
|
||||
|
||||
/// A map of style properties.
|
||||
#[derive(Default, Clone, Hash)]
|
||||
#[derive(Default, Clone, PartialEq, Hash)]
|
||||
pub struct StyleMap(Vec<Entry>);
|
||||
|
||||
impl StyleMap {
|
||||
@ -93,7 +59,7 @@ impl StyleMap {
|
||||
*outer
|
||||
} else {
|
||||
StyleChain {
|
||||
first: Link::Map(self),
|
||||
link: Some(Link::Map(self)),
|
||||
outer: Some(outer),
|
||||
}
|
||||
}
|
||||
@ -103,31 +69,16 @@ impl StyleMap {
|
||||
/// equivalent to the style chain created by
|
||||
/// `self.chain(StyleChain::new(outer))`.
|
||||
///
|
||||
/// This is useful in the evaluation phase while building nodes and their
|
||||
/// style maps, whereas `chain` would be used during layouting to combine
|
||||
/// immutable style maps from different levels of the hierarchy.
|
||||
/// This is useful over `chain` when you need an owned map without a
|
||||
/// lifetime, for example, because you want to store the style map inside a
|
||||
/// packed node.
|
||||
pub fn apply(&mut self, outer: &Self) {
|
||||
self.0.splice(0 .. 0, outer.0.clone());
|
||||
}
|
||||
|
||||
/// Subtract `other` from `self` in-place, keeping only styles that are in
|
||||
/// `self` but not in `other`.
|
||||
pub fn erase(&mut self, other: &Self) {
|
||||
self.0.retain(|x| !other.0.contains(x));
|
||||
}
|
||||
|
||||
/// Intersect `self` with `other` in-place, keeping only styles that are
|
||||
/// both in `self` and `other`.
|
||||
pub fn intersect(&mut self, other: &Self) {
|
||||
self.0.retain(|x| other.0.contains(x));
|
||||
}
|
||||
|
||||
/// Whether two style maps are equal when filtered down to properties of the
|
||||
/// node `T`.
|
||||
pub fn compatible<T: 'static>(&self, other: &Self) -> bool {
|
||||
let f = |entry: &&Entry| entry.is_of::<T>();
|
||||
self.0.iter().filter(f).count() == other.0.iter().filter(f).count()
|
||||
&& self.0.iter().filter(f).all(|x| other.0.contains(x))
|
||||
/// The highest-level interruption of the map.
|
||||
pub fn interruption(&self) -> Option<Interruption> {
|
||||
self.0.iter().filter_map(|entry| entry.interruption()).max()
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,10 +91,13 @@ impl Debug for StyleMap {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for StyleMap {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.len() == other.0.len() && self.0.iter().all(|x| other.0.contains(x))
|
||||
}
|
||||
/// Determines whether a style could interrupt some composable structure.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub enum Interruption {
|
||||
/// The style forces a paragraph break.
|
||||
Par,
|
||||
/// The style forces a page break.
|
||||
Page,
|
||||
}
|
||||
|
||||
/// A chain of style maps, similar to a linked list.
|
||||
@ -153,10 +107,10 @@ impl PartialEq for StyleMap {
|
||||
/// eagerly merging the maps, each access walks the hierarchy from the innermost
|
||||
/// to the outermost map, trying to find a match and then folding it with
|
||||
/// matches further up the chain.
|
||||
#[derive(Clone, Copy, Hash)]
|
||||
#[derive(Default, Clone, Copy, Hash)]
|
||||
pub struct StyleChain<'a> {
|
||||
/// The first link in the chain.
|
||||
first: Link<'a>,
|
||||
/// The first link of this chain.
|
||||
link: Option<Link<'a>>,
|
||||
/// The remaining links in the chain.
|
||||
outer: Option<&'a Self>,
|
||||
}
|
||||
@ -173,10 +127,52 @@ enum Link<'a> {
|
||||
|
||||
impl<'a> StyleChain<'a> {
|
||||
/// Start a new style chain with a root map.
|
||||
pub fn new(first: &'a StyleMap) -> Self {
|
||||
Self { first: Link::Map(first), outer: None }
|
||||
pub fn new(map: &'a StyleMap) -> Self {
|
||||
Self { link: Some(Link::Map(map)), outer: None }
|
||||
}
|
||||
|
||||
/// The number of links in the chain.
|
||||
pub fn len(self) -> usize {
|
||||
self.links().count()
|
||||
}
|
||||
|
||||
/// Convert to an owned style map.
|
||||
///
|
||||
/// Panics if the chain contains barrier links.
|
||||
pub fn to_map(self) -> StyleMap {
|
||||
let mut suffix = StyleMap::new();
|
||||
for link in self.links() {
|
||||
match link {
|
||||
Link::Map(map) => suffix.apply(map),
|
||||
Link::Barrier(_) => panic!("chain contains barrier"),
|
||||
}
|
||||
}
|
||||
suffix
|
||||
}
|
||||
|
||||
/// Build a style map from the suffix (all links beyond the `len`) of the
|
||||
/// chain.
|
||||
///
|
||||
/// Panics if the suffix contains barrier links.
|
||||
pub fn suffix(self, len: usize) -> StyleMap {
|
||||
let mut suffix = StyleMap::new();
|
||||
let remove = self.len().saturating_sub(len);
|
||||
for link in self.links().take(remove) {
|
||||
match link {
|
||||
Link::Map(map) => suffix.apply(map),
|
||||
Link::Barrier(_) => panic!("suffix contains barrier"),
|
||||
}
|
||||
}
|
||||
suffix
|
||||
}
|
||||
|
||||
/// Remove the last link from the chain.
|
||||
pub fn pop(&mut self) {
|
||||
*self = self.outer.copied().unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> StyleChain<'a> {
|
||||
/// Get the (folded) value of a copyable style property.
|
||||
///
|
||||
/// This is the method you should reach for first. If it doesn't work
|
||||
@ -235,17 +231,19 @@ impl<'a> StyleChain<'a> {
|
||||
pub fn barred<'b>(&'b self, node: TypeId) -> StyleChain<'b> {
|
||||
if self
|
||||
.maps()
|
||||
.any(|map| map.0.iter().any(|entry| entry.scoped && entry.is_of_same(node)))
|
||||
.any(|map| map.0.iter().any(|entry| entry.scoped && entry.is_of_id(node)))
|
||||
{
|
||||
StyleChain {
|
||||
first: Link::Barrier(node),
|
||||
link: Some(Link::Barrier(node)),
|
||||
outer: Some(self),
|
||||
}
|
||||
} else {
|
||||
*self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> StyleChain<'a> {
|
||||
/// Iterate over all values for the given property in the chain.
|
||||
fn values<P: Property>(self, _: P) -> impl Iterator<Item = &'a P::Value> {
|
||||
let mut depth = 0;
|
||||
@ -263,16 +261,6 @@ impl<'a> StyleChain<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterate over the links of the chain.
|
||||
fn links(self) -> impl Iterator<Item = Link<'a>> {
|
||||
let mut cursor = Some(self);
|
||||
std::iter::from_fn(move || {
|
||||
let Self { first, outer } = cursor?;
|
||||
cursor = outer.copied();
|
||||
Some(first)
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterate over the map links of the chain.
|
||||
fn maps(self) -> impl Iterator<Item = &'a StyleMap> {
|
||||
self.links().filter_map(|link| match link {
|
||||
@ -280,6 +268,16 @@ impl<'a> StyleChain<'a> {
|
||||
Link::Barrier(_) => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterate over the links of the chain.
|
||||
fn links(self) -> impl Iterator<Item = Link<'a>> {
|
||||
let mut cursor = Some(self);
|
||||
std::iter::from_fn(move || {
|
||||
let Self { link, outer } = cursor?;
|
||||
cursor = outer.copied();
|
||||
link
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for StyleChain<'_> {
|
||||
@ -300,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.
|
||||
#[derive(Clone)]
|
||||
struct Entry {
|
||||
@ -323,13 +507,23 @@ impl Entry {
|
||||
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
|
||||
}
|
||||
|
||||
fn downcast<P: Property>(&self) -> Option<&P::Value> {
|
||||
self.pair.as_any().downcast_ref()
|
||||
}
|
||||
|
||||
fn interruption(&self) -> Option<Interruption> {
|
||||
if self.is_of::<PageNode>() {
|
||||
Some(Interruption::Page)
|
||||
} else if self.is_of::<ParNode>() {
|
||||
Some(Interruption::Par)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Entry {
|
||||
@ -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
|
||||
/// 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
|
||||
|
@ -1,11 +1,12 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::iter::Sum;
|
||||
use std::mem;
|
||||
use std::ops::{Add, AddAssign};
|
||||
|
||||
use super::{Property, StyleMap, Styled};
|
||||
use typed_arena::Arena;
|
||||
|
||||
use super::{CollapsingBuilder, Interruption, Property, StyleMap, StyleVecBuilder};
|
||||
use crate::diag::StrResult;
|
||||
use crate::layout::{Layout, PackedNode};
|
||||
use crate::library::prelude::*;
|
||||
@ -50,16 +51,16 @@ pub enum Template {
|
||||
Horizontal(SpacingKind),
|
||||
/// Plain text.
|
||||
Text(EcoString),
|
||||
/// An inline node.
|
||||
/// An inline-level node.
|
||||
Inline(PackedNode),
|
||||
/// A paragraph break.
|
||||
Parbreak,
|
||||
/// Vertical spacing.
|
||||
Vertical(SpacingKind),
|
||||
/// A block node.
|
||||
Block(PackedNode),
|
||||
/// A column break.
|
||||
Colbreak,
|
||||
/// Vertical spacing.
|
||||
Vertical(SpacingKind),
|
||||
/// A block-level node.
|
||||
Block(PackedNode),
|
||||
/// A page break.
|
||||
Pagebreak,
|
||||
/// A page node.
|
||||
@ -95,12 +96,11 @@ impl Template {
|
||||
/// Layout this template into a collection of pages.
|
||||
pub fn layout(&self, ctx: &mut Context) -> Vec<Arc<Frame>> {
|
||||
let (mut ctx, styles) = LayoutContext::new(ctx);
|
||||
let mut packer = Packer::new(true);
|
||||
packer.walk(self.clone(), StyleMap::new());
|
||||
packer
|
||||
.into_root()
|
||||
let (pages, shared) = Builder::build_pages(self);
|
||||
let styles = shared.chain(&styles);
|
||||
pages
|
||||
.iter()
|
||||
.flat_map(|styled| styled.item.layout(&mut ctx, styled.map.chain(&styles)))
|
||||
.flat_map(|(page, map)| page.layout(&mut ctx, map.chain(&styles)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@ -159,13 +159,13 @@ impl Debug for Template {
|
||||
f.write_str(")")
|
||||
}
|
||||
Self::Parbreak => f.pad("Parbreak"),
|
||||
Self::Colbreak => f.pad("Colbreak"),
|
||||
Self::Vertical(kind) => write!(f, "Vertical({kind:?})"),
|
||||
Self::Block(node) => {
|
||||
f.write_str("Block(")?;
|
||||
node.fmt(f)?;
|
||||
f.write_str(")")
|
||||
}
|
||||
Self::Colbreak => f.pad("Colbreak"),
|
||||
Self::Pagebreak => f.pad("Pagebreak"),
|
||||
Self::Page(page) => page.fmt(f),
|
||||
Self::Styled(sub, map) => {
|
||||
@ -220,338 +220,172 @@ impl Layout for Template {
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
let mut packer = Packer::new(false);
|
||||
packer.walk(self.clone(), StyleMap::new());
|
||||
packer.into_block().layout(ctx, regions, styles)
|
||||
let (flow, shared) = Builder::build_flow(self);
|
||||
flow.layout(ctx, regions, shared.chain(&styles))
|
||||
}
|
||||
|
||||
fn pack(self) -> PackedNode {
|
||||
if let Template::Block(packed) = self {
|
||||
packed
|
||||
} else {
|
||||
PackedNode::new(self)
|
||||
match self {
|
||||
Template::Block(node) => node,
|
||||
other => PackedNode::new(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Packs a [`Template`] into a flow or root node.
|
||||
struct Packer {
|
||||
/// Whether this packer produces a root node.
|
||||
top: bool,
|
||||
/// The accumulated page nodes.
|
||||
pages: Vec<Styled<PageNode>>,
|
||||
/// The accumulated flow children.
|
||||
flow: Builder<Styled<FlowChild>>,
|
||||
/// The accumulated paragraph children.
|
||||
par: Builder<Styled<ParChild>>,
|
||||
/// Builds a flow or page nodes from a template.
|
||||
struct Builder<'a> {
|
||||
/// An arena where intermediate style chains are stored.
|
||||
arena: &'a Arena<StyleChain<'a>>,
|
||||
/// The already built page runs.
|
||||
pages: Option<StyleVecBuilder<'a, PageNode>>,
|
||||
/// The currently built flow.
|
||||
flow: CollapsingBuilder<'a, FlowChild>,
|
||||
/// The currently built paragraph.
|
||||
par: CollapsingBuilder<'a, ParChild>,
|
||||
/// Whether to keep the next page even if it is empty.
|
||||
keep_next: bool,
|
||||
}
|
||||
|
||||
impl Packer {
|
||||
/// Start a new template-packing session.
|
||||
fn new(top: bool) -> Self {
|
||||
impl<'a> Builder<'a> {
|
||||
/// Build page runs from a template.
|
||||
fn build_pages(template: &Template) -> (StyleVec<PageNode>, StyleMap) {
|
||||
let arena = Arena::new();
|
||||
|
||||
let mut builder = Builder::prepare(&arena, true);
|
||||
builder.process(template, StyleChain::default());
|
||||
builder.finish_page(true, false, StyleChain::default());
|
||||
|
||||
let (pages, shared) = builder.pages.unwrap().finish();
|
||||
(pages, shared.to_map())
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
top,
|
||||
pages: vec![],
|
||||
flow: Builder::default(),
|
||||
par: Builder::default(),
|
||||
arena,
|
||||
pages: top.then(|| StyleVecBuilder::new()),
|
||||
flow: CollapsingBuilder::new(),
|
||||
par: CollapsingBuilder::new(),
|
||||
keep_next: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Finish up and return the resulting flow.
|
||||
fn into_block(mut self) -> PackedNode {
|
||||
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) {
|
||||
/// Process a template.
|
||||
fn process(&mut self, template: &'a Template, styles: StyleChain<'a>) {
|
||||
match template {
|
||||
Template::Space => {
|
||||
// A text space is "soft", meaning that it can be eaten up by
|
||||
// adjacent line breaks or explicit spacings.
|
||||
self.par.last.soft(Styled::new(ParChild::text(' '), styles), false);
|
||||
self.par.weak(ParChild::Text(' '.into()), styles);
|
||||
}
|
||||
Template::Linebreak => {
|
||||
// A line break eats up surrounding text spaces.
|
||||
self.par.last.hard();
|
||||
self.push_inline(Styled::new(ParChild::text('\n'), styles));
|
||||
self.par.last.hard();
|
||||
}
|
||||
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));
|
||||
self.par.destructive(ParChild::Text('\n'.into()), styles);
|
||||
}
|
||||
Template::Horizontal(kind) => {
|
||||
// Just like a line break, explicit horizontal spacing eats up
|
||||
// surrounding text spaces.
|
||||
self.par.last.hard();
|
||||
self.push_inline(Styled::new(ParChild::Spacing(kind), styles));
|
||||
self.par.last.hard();
|
||||
let child = ParChild::Spacing(*kind);
|
||||
if kind.is_fractional() {
|
||||
self.par.destructive(child, styles);
|
||||
} else {
|
||||
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) => {
|
||||
// Explicit vertical spacing ends the current paragraph and then
|
||||
// discards the paragraph break.
|
||||
self.parbreak(None, false);
|
||||
self.make_flow_compatible(&styles);
|
||||
self.flow.children.push(Styled::new(FlowChild::Spacing(kind), styles));
|
||||
self.flow.last.hard();
|
||||
self.finish_par();
|
||||
let child = FlowChild::Spacing(*kind);
|
||||
if kind.is_fractional() {
|
||||
self.flow.destructive(child, styles);
|
||||
} else {
|
||||
self.flow.ignorant(child, styles);
|
||||
}
|
||||
}
|
||||
Template::Inline(inline) => {
|
||||
self.push_inline(Styled::new(ParChild::Node(inline), styles));
|
||||
Template::Block(node) => {
|
||||
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) => {
|
||||
self.push_block(Styled::new(block, styles));
|
||||
Template::Pagebreak => {
|
||||
self.finish_page(true, true, styles);
|
||||
}
|
||||
Template::Page(page) => {
|
||||
if self.top {
|
||||
self.pagebreak();
|
||||
self.pages.push(Styled::new(page, styles));
|
||||
} else {
|
||||
self.push_block(Styled::new(page.0, styles));
|
||||
self.finish_page(false, false, styles);
|
||||
if let Some(pages) = &mut self.pages {
|
||||
pages.push(page.clone(), styles);
|
||||
}
|
||||
}
|
||||
Template::Styled(template, mut map) => {
|
||||
map.apply(&styles);
|
||||
self.walk(*template, map);
|
||||
Template::Styled(sub, map) => {
|
||||
let interruption = map.interruption();
|
||||
match interruption {
|
||||
Some(Interruption::Page) => self.finish_page(false, true, styles),
|
||||
Some(Interruption::Par) => self.finish_par(),
|
||||
None => {}
|
||||
}
|
||||
|
||||
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) => {
|
||||
// For a list of templates, we apply the list's styles to each
|
||||
// templates individually.
|
||||
for item in seq {
|
||||
self.walk(item, styles.clone());
|
||||
for sub in seq {
|
||||
self.process(sub, styles);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert an inline-level element into the current paragraph.
|
||||
fn push_inline(&mut self, child: Styled<ParChild>) {
|
||||
// The child's map must be both compatible with the current page and the
|
||||
// current paragraph.
|
||||
self.make_flow_compatible(&child.map);
|
||||
self.make_par_compatible(&child.map);
|
||||
|
||||
if let Some(styled) = self.par.last.any() {
|
||||
self.push_coalescing(styled);
|
||||
/// Finish the currently built paragraph.
|
||||
fn finish_par(&mut self) {
|
||||
let (par, shared) = mem::take(&mut self.par).finish();
|
||||
if !par.is_empty() {
|
||||
let node = ParNode(par).pack();
|
||||
self.flow.supportive(FlowChild::Node(node), shared);
|
||||
}
|
||||
|
||||
self.push_coalescing(child);
|
||||
self.par.last.any();
|
||||
}
|
||||
|
||||
/// Push a paragraph child, coalescing text nodes with compatible styles.
|
||||
fn push_coalescing(&mut self, child: Styled<ParChild>) {
|
||||
if let ParChild::Text(right) = &child.item {
|
||||
if let Some(Styled { item: ParChild::Text(left), map }) =
|
||||
self.par.children.last_mut()
|
||||
{
|
||||
if child.map.compatible::<TextNode>(map) {
|
||||
left.0.push_str(&right.0);
|
||||
return;
|
||||
}
|
||||
/// Finish the currently built page run.
|
||||
fn finish_page(&mut self, keep_last: bool, keep_next: bool, styles: StyleChain<'a>) {
|
||||
self.finish_par();
|
||||
if let Some(pages) = &mut self.pages {
|
||||
let (flow, shared) = mem::take(&mut self.flow).finish();
|
||||
if !flow.is_empty() || (keep_last && self.keep_next) {
|
||||
let styles = if flow.is_empty() { styles } else { shared };
|
||||
let node = PageNode(FlowNode(flow).pack());
|
||||
pages.push(node, styles);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
self.keep_next = keep_next;
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,27 @@
|
||||
//! A flow of paragraphs and other block-level nodes.
|
||||
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
use super::prelude::*;
|
||||
use super::{AlignNode, ParNode, PlaceNode, SpacingKind, TextNode};
|
||||
|
||||
/// A vertical flow of content consisting of paragraphs and other layout nodes.
|
||||
/// Arrange spacing, paragraphs and other block-level nodes into a flow.
|
||||
///
|
||||
/// This node is reponsible for layouting both the top-level content flow and
|
||||
/// the contents of boxes.
|
||||
#[derive(Hash)]
|
||||
pub struct FlowNode(pub Vec<Styled<FlowChild>>);
|
||||
pub struct FlowNode(pub StyleVec<FlowChild>);
|
||||
|
||||
/// A child of a flow node.
|
||||
#[derive(Hash)]
|
||||
pub enum FlowChild {
|
||||
/// A paragraph / block break.
|
||||
Parbreak,
|
||||
/// A column / region break.
|
||||
Colbreak,
|
||||
/// Vertical spacing between other children.
|
||||
Spacing(SpacingKind),
|
||||
/// An arbitrary block-level node.
|
||||
Node(PackedNode),
|
||||
}
|
||||
|
||||
impl Layout for FlowNode {
|
||||
fn layout(
|
||||
@ -19,45 +30,58 @@ impl Layout for FlowNode {
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
FlowLayouter::new(self, regions.clone()).layout(ctx, styles)
|
||||
let mut layouter = FlowLayouter::new(regions);
|
||||
|
||||
for (child, map) in self.0.iter() {
|
||||
let styles = map.chain(&styles);
|
||||
match child {
|
||||
FlowChild::Parbreak => {
|
||||
let em = styles.get(TextNode::SIZE).abs;
|
||||
let amount = styles.get(ParNode::SPACING).resolve(em);
|
||||
layouter.layout_spacing(SpacingKind::Linear(amount.into()));
|
||||
}
|
||||
FlowChild::Colbreak => {
|
||||
layouter.finish_region();
|
||||
}
|
||||
FlowChild::Spacing(kind) => {
|
||||
layouter.layout_spacing(*kind);
|
||||
}
|
||||
FlowChild::Node(ref node) => {
|
||||
layouter.layout_node(ctx, node, styles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layouter.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Merge for FlowChild {
|
||||
fn merge(&mut self, _: &Self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for FlowNode {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.write_str("Flow ")?;
|
||||
f.debug_list().entries(&self.0).finish()
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// A child of a flow node.
|
||||
#[derive(Hash)]
|
||||
pub enum FlowChild {
|
||||
/// A paragraph/block break.
|
||||
Break,
|
||||
/// Skip the rest of the region and move to the next.
|
||||
Skip,
|
||||
/// Vertical spacing between other children.
|
||||
Spacing(SpacingKind),
|
||||
/// An arbitrary node.
|
||||
Node(PackedNode),
|
||||
}
|
||||
|
||||
impl Debug for FlowChild {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Break => f.pad("Break"),
|
||||
Self::Skip => f.pad("Skip"),
|
||||
Self::Spacing(kind) => kind.fmt(f),
|
||||
Self::Parbreak => f.pad("Parbreak"),
|
||||
Self::Colbreak => f.pad("Colbreak"),
|
||||
Self::Spacing(kind) => write!(f, "{:?}", kind),
|
||||
Self::Node(node) => node.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs flow layout.
|
||||
struct FlowLayouter<'a> {
|
||||
/// The children of the flow.
|
||||
children: &'a [Styled<FlowChild>],
|
||||
pub struct FlowLayouter {
|
||||
/// The regions to layout children into.
|
||||
regions: Regions,
|
||||
/// Whether the flow should expand to fill the region.
|
||||
@ -69,6 +93,8 @@ struct FlowLayouter<'a> {
|
||||
used: Size,
|
||||
/// The sum of fractional ratios in the current region.
|
||||
fr: Fractional,
|
||||
/// Whether to add leading before the next node.
|
||||
leading: bool,
|
||||
/// Spacing and layouted nodes.
|
||||
items: Vec<FlowItem>,
|
||||
/// Finished frames for previous regions.
|
||||
@ -87,98 +113,64 @@ enum FlowItem {
|
||||
Placed(Arc<Frame>),
|
||||
}
|
||||
|
||||
impl<'a> FlowLayouter<'a> {
|
||||
impl FlowLayouter {
|
||||
/// Create a new flow layouter.
|
||||
fn new(flow: &'a FlowNode, mut regions: Regions) -> Self {
|
||||
pub fn new(regions: &Regions) -> Self {
|
||||
let expand = regions.expand;
|
||||
let full = regions.current;
|
||||
|
||||
// Disable vertical expansion for children.
|
||||
let mut regions = regions.clone();
|
||||
regions.expand.y = false;
|
||||
|
||||
Self {
|
||||
children: &flow.0,
|
||||
regions,
|
||||
expand,
|
||||
full,
|
||||
used: Size::zero(),
|
||||
fr: Fractional::zero(),
|
||||
leading: false,
|
||||
items: vec![],
|
||||
finished: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Layout all children.
|
||||
fn layout(
|
||||
mut self,
|
||||
ctx: &mut LayoutContext,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
for styled in self.children {
|
||||
let styles = styled.map.chain(&styles);
|
||||
match styled.item {
|
||||
FlowChild::Break => {
|
||||
let em = styles.get(TextNode::SIZE).abs;
|
||||
let amount = styles.get(ParNode::SPACING).resolve(em);
|
||||
self.layout_absolute(amount.into());
|
||||
}
|
||||
FlowChild::Skip => {
|
||||
self.finish_region();
|
||||
}
|
||||
FlowChild::Spacing(kind) => {
|
||||
self.layout_spacing(kind);
|
||||
}
|
||||
FlowChild::Node(ref node) => {
|
||||
if self.regions.is_full() {
|
||||
self.finish_region();
|
||||
}
|
||||
|
||||
self.layout_node(ctx, node, styles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.expand.y {
|
||||
while self.regions.backlog.len() > 0 {
|
||||
self.finish_region();
|
||||
}
|
||||
}
|
||||
|
||||
self.finish_region();
|
||||
self.finished
|
||||
}
|
||||
|
||||
/// Layout spacing.
|
||||
fn layout_spacing(&mut self, spacing: SpacingKind) {
|
||||
pub fn layout_spacing(&mut self, spacing: SpacingKind) {
|
||||
match spacing {
|
||||
SpacingKind::Linear(v) => self.layout_absolute(v),
|
||||
SpacingKind::Linear(v) => {
|
||||
// Resolve the linear and limit it to the remaining space.
|
||||
let resolved = v.resolve(self.full.y);
|
||||
let limited = resolved.min(self.regions.current.y);
|
||||
self.regions.current.y -= limited;
|
||||
self.used.y += limited;
|
||||
self.items.push(FlowItem::Absolute(resolved));
|
||||
}
|
||||
SpacingKind::Fractional(v) => {
|
||||
self.items.push(FlowItem::Fractional(v));
|
||||
self.fr += v;
|
||||
self.leading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Layout absolute spacing.
|
||||
fn layout_absolute(&mut self, amount: Linear) {
|
||||
// Resolve the linear, limiting it to the remaining available space.
|
||||
let resolved = amount.resolve(self.full.y);
|
||||
let limited = resolved.min(self.regions.current.y);
|
||||
self.regions.current.y -= limited;
|
||||
self.used.y += limited;
|
||||
self.items.push(FlowItem::Absolute(resolved));
|
||||
}
|
||||
|
||||
/// Layout a node.
|
||||
fn layout_node(
|
||||
pub fn layout_node(
|
||||
&mut self,
|
||||
ctx: &mut LayoutContext,
|
||||
node: &PackedNode,
|
||||
styles: StyleChain,
|
||||
) {
|
||||
// Don't even try layouting into a full region.
|
||||
if self.regions.is_full() {
|
||||
self.finish_region();
|
||||
}
|
||||
|
||||
// Placed nodes that are out of flow produce placed items which aren't
|
||||
// aligned later.
|
||||
let mut is_placed = false;
|
||||
if let Some(placed) = node.downcast::<PlaceNode>() {
|
||||
is_placed = true;
|
||||
if placed.out_of_flow() {
|
||||
let frame = node.layout(ctx, &self.regions, styles).remove(0);
|
||||
self.items.push(FlowItem::Placed(frame.item));
|
||||
@ -186,6 +178,13 @@ impl<'a> FlowLayouter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// Add leading.
|
||||
if self.leading {
|
||||
let em = styles.get(TextNode::SIZE).abs;
|
||||
let amount = styles.get(ParNode::LEADING).resolve(em);
|
||||
self.layout_spacing(SpacingKind::Linear(amount.into()));
|
||||
}
|
||||
|
||||
// How to align the node.
|
||||
let aligns = Spec::new(
|
||||
// For non-expanding paragraphs it is crucial that we align the
|
||||
@ -211,10 +210,12 @@ impl<'a> FlowLayouter<'a> {
|
||||
self.finish_region();
|
||||
}
|
||||
}
|
||||
|
||||
self.leading = !is_placed;
|
||||
}
|
||||
|
||||
/// Finish the frame for one region.
|
||||
fn finish_region(&mut self) {
|
||||
pub fn finish_region(&mut self) {
|
||||
// Determine the size of the flow in this region dependening on whether
|
||||
// the region expands.
|
||||
let mut size = self.expand.select(self.full, self.used);
|
||||
@ -263,6 +264,19 @@ impl<'a> FlowLayouter<'a> {
|
||||
self.full = self.regions.current;
|
||||
self.used = Size::zero();
|
||||
self.fr = Fractional::zero();
|
||||
self.leading = false;
|
||||
self.finished.push(output.constrain(cts));
|
||||
}
|
||||
|
||||
/// Finish layouting and return the resulting frames.
|
||||
pub fn finish(mut self) -> Vec<Constrained<Arc<Frame>>> {
|
||||
if self.expand.y {
|
||||
while self.regions.backlog.len() > 0 {
|
||||
self.finish_region();
|
||||
}
|
||||
}
|
||||
|
||||
self.finish_region();
|
||||
self.finished
|
||||
}
|
||||
}
|
||||
|
@ -68,8 +68,8 @@ prelude! {
|
||||
|
||||
pub use crate::diag::{At, TypResult};
|
||||
pub use crate::eval::{
|
||||
Args, Construct, EvalContext, Template, Property, Scope, Set, Smart, StyleChain,
|
||||
StyleMap, Styled, Value,
|
||||
Args, Construct, EvalContext, Merge, Property, Scope, Set, Smart, StyleChain,
|
||||
StyleMap, StyleVec, Template, Value,
|
||||
};
|
||||
pub use crate::frame::*;
|
||||
pub use crate::geom::*;
|
||||
|
@ -1,6 +1,5 @@
|
||||
//! Paragraph layout.
|
||||
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::sync::Arc;
|
||||
|
||||
use itertools::Either;
|
||||
@ -11,9 +10,20 @@ use super::prelude::*;
|
||||
use super::{shape, ShapedText, SpacingKind, TextNode};
|
||||
use crate::util::{ArcExt, EcoString, RangeExt, SliceExt};
|
||||
|
||||
/// Arrange text, spacing and inline nodes into a paragraph.
|
||||
/// Arrange text, spacing and inline-level nodes into a paragraph.
|
||||
#[derive(Hash)]
|
||||
pub struct ParNode(pub Vec<Styled<ParChild>>);
|
||||
pub struct ParNode(pub StyleVec<ParChild>);
|
||||
|
||||
/// A uniformly styled atomic piece of a paragraph.
|
||||
#[derive(Hash)]
|
||||
pub enum ParChild {
|
||||
/// A chunk of text.
|
||||
Text(EcoString),
|
||||
/// Horizontal spacing between other children.
|
||||
Spacing(SpacingKind),
|
||||
/// An arbitrary inline-level node.
|
||||
Node(PackedNode),
|
||||
}
|
||||
|
||||
#[class]
|
||||
impl ParNode {
|
||||
@ -23,8 +33,8 @@ impl ParNode {
|
||||
pub const ALIGN: Align = Align::Left;
|
||||
/// The spacing between lines (dependent on scaled font size).
|
||||
pub const LEADING: Linear = Relative::new(0.65).into();
|
||||
/// The spacing between paragraphs (dependent on scaled font size).
|
||||
pub const SPACING: Linear = Relative::new(1.2).into();
|
||||
/// The extra spacing between paragraphs (dependent on scaled font size).
|
||||
pub const SPACING: Linear = Relative::new(0.55).into();
|
||||
|
||||
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
|
||||
// The paragraph constructor is special: It doesn't create a paragraph
|
||||
@ -116,9 +126,9 @@ impl ParNode {
|
||||
|
||||
/// The string representation of each child.
|
||||
fn strings(&self) -> impl Iterator<Item = &str> {
|
||||
self.0.iter().map(|styled| match &styled.item {
|
||||
self.0.items().map(|child| match child {
|
||||
ParChild::Text(text) => text,
|
||||
ParChild::Spacing(_) => " ",
|
||||
ParChild::Text(text) => &text.0,
|
||||
ParChild::Node(_) => "\u{FFFC}",
|
||||
})
|
||||
}
|
||||
@ -127,38 +137,31 @@ impl ParNode {
|
||||
impl Debug for ParNode {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.write_str("Par ")?;
|
||||
f.debug_list().entries(&self.0).finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// A child of a paragraph node.
|
||||
#[derive(Hash)]
|
||||
pub enum ParChild {
|
||||
/// Spacing between other nodes.
|
||||
Spacing(SpacingKind),
|
||||
/// A run of text and how to align it in its line.
|
||||
Text(TextNode),
|
||||
/// Any child node and how to align it in its line.
|
||||
Node(PackedNode),
|
||||
}
|
||||
|
||||
impl ParChild {
|
||||
/// Create a text child.
|
||||
pub fn text(text: impl Into<EcoString>) -> Self {
|
||||
Self::Text(TextNode(text.into()))
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ParChild {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Spacing(kind) => kind.fmt(f),
|
||||
Self::Text(text) => text.fmt(f),
|
||||
Self::Text(text) => write!(f, "Text({:?})", text),
|
||||
Self::Spacing(kind) => write!(f, "{:?}", kind),
|
||||
Self::Node(node) => node.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Merge for ParChild {
|
||||
fn merge(&mut self, next: &Self) -> bool {
|
||||
if let (Self::Text(left), Self::Text(right)) = (self, next) {
|
||||
left.push_str(right);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A paragraph break.
|
||||
pub struct ParbreakNode;
|
||||
|
||||
@ -222,20 +225,9 @@ impl<'a> ParLayouter<'a> {
|
||||
let mut ranges = vec![];
|
||||
|
||||
// Layout the children and collect them into items.
|
||||
for (range, styled) in par.ranges().zip(&par.0) {
|
||||
let styles = styled.map.chain(styles);
|
||||
match styled.item {
|
||||
ParChild::Spacing(kind) => match kind {
|
||||
SpacingKind::Linear(v) => {
|
||||
let resolved = v.resolve(regions.current.x);
|
||||
items.push(ParItem::Absolute(resolved));
|
||||
ranges.push(range);
|
||||
}
|
||||
SpacingKind::Fractional(v) => {
|
||||
items.push(ParItem::Fractional(v));
|
||||
ranges.push(range);
|
||||
}
|
||||
},
|
||||
for (range, (child, map)) in par.ranges().zip(par.0.iter()) {
|
||||
let styles = map.chain(styles);
|
||||
match child {
|
||||
ParChild::Text(_) => {
|
||||
// TODO: Also split by language and script.
|
||||
let mut cursor = range.start;
|
||||
@ -249,7 +241,18 @@ impl<'a> ParLayouter<'a> {
|
||||
ranges.push(subrange);
|
||||
}
|
||||
}
|
||||
ParChild::Node(ref node) => {
|
||||
ParChild::Spacing(kind) => match *kind {
|
||||
SpacingKind::Linear(v) => {
|
||||
let resolved = v.resolve(regions.current.x);
|
||||
items.push(ParItem::Absolute(resolved));
|
||||
ranges.push(range);
|
||||
}
|
||||
SpacingKind::Fractional(v) => {
|
||||
items.push(ParItem::Fractional(v));
|
||||
ranges.push(range);
|
||||
}
|
||||
},
|
||||
ParChild::Node(node) => {
|
||||
let size = Size::new(regions.current.x, regions.base.y);
|
||||
let pod = Regions::one(size, regions.base, Spec::splat(false));
|
||||
let frame = node.layout(ctx, &pod, styles).remove(0);
|
||||
|
@ -31,6 +31,13 @@ pub enum SpacingKind {
|
||||
Fractional(Fractional),
|
||||
}
|
||||
|
||||
impl SpacingKind {
|
||||
/// Whether this is fractional spacing.
|
||||
pub fn is_fractional(self) -> bool {
|
||||
matches!(self, Self::Fractional(_))
|
||||
}
|
||||
}
|
||||
|
||||
castable! {
|
||||
SpacingKind,
|
||||
Expected: "linear or fractional",
|
||||
|
@ -19,7 +19,7 @@ use crate::util::{EcoString, SliceExt};
|
||||
|
||||
/// A single run of text with the same style.
|
||||
#[derive(Hash)]
|
||||
pub struct TextNode(pub EcoString);
|
||||
pub struct TextNode;
|
||||
|
||||
#[class]
|
||||
impl TextNode {
|
||||
@ -144,12 +144,6 @@ impl TextNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for TextNode {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "Text({:?})", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Strong text, rendered in boldface.
|
||||
pub struct StrongNode;
|
||||
|
||||
|
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 \
|
||||
Ola Nordmann, John Doe
|
||||
|
||||
#v(6mm)
|
||||
#v(2mm)
|
||||
#align(center)[
|
||||
==== 3. Übungsblatt Computerorientierte Mathematik II
|
||||
#v(4mm)
|
||||
*Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) #v(4mm)
|
||||
==== 3. Übungsblatt Computerorientierte Mathematik II #v(1mm)
|
||||
*Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) #v(1mm)
|
||||
*Alle Antworten sind zu beweisen.*
|
||||
]
|
||||
|
||||
@ -21,5 +20,4 @@ Die Tiefe eines Knotens _v_ ist die Länge des eindeutigen Weges von der Wurzel
|
||||
zu _v_, und die Höhe von _v_ ist die Länge eines längsten (absteigenden) Weges
|
||||
von _v_ zu einem Blatt. Die Höhe des Baumes ist die Höhe der Wurzel.
|
||||
|
||||
#v(6mm)
|
||||
#align(center, image("../res/graph.png", width: 75%))
|
||||
|
@ -15,6 +15,7 @@ Apart
|
||||
#set page(height: 60pt)
|
||||
|
||||
First!
|
||||
|
||||
#block[
|
||||
But, soft! what light through yonder window breaks? It is the east, and Juliet
|
||||
is the sun.
|
||||
|
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.
|
||||
// Should result in one high and one wide page.
|
||||
#set page(width: 80pt, height: 80pt)
|
||||
[#set page(width: 40pt);High]
|
||||
[#set page(height: 40pt);Wide]
|
||||
|
||||
// Set all margins at once.
|
||||
[
|
||||
#set page(margins: 5pt)
|
||||
#place(top + left)[TL]
|
||||
#place(bottom + right)[BR]
|
||||
]
|
||||
|
||||
// Set individual margins.
|
||||
#set page(height: 40pt)
|
||||
[#set page(left: 0pt); #align(left)[Left]]
|
||||
[#set page(right: 0pt); #align(right)[Right]]
|
||||
[#set page(top: 0pt); #align(top)[Top]]
|
||||
[#set page(bottom: 0pt); #align(bottom)[Bottom]]
|
||||
|
||||
// Ensure that specific margins override general margins.
|
||||
[#set page(margins: 0pt, left: 20pt); Overriden]
|
||||
|
||||
// Flipped predefined paper.
|
||||
[#set page(paper: "a11", flipped: true);Flipped A11]
|
||||
|
||||
---
|
||||
// Test page fill.
|
||||
#set page(width: 80pt, height: 40pt, fill: eastern)
|
||||
#text(15pt, "Roboto", fill: white, smallcaps: true)[Typst]
|
||||
#page(width: 40pt, fill: none, margins: auto, top: 10pt)[Hi]
|
||||
|
||||
#set page(width: 40pt, fill: none, margins: auto, top: 10pt)
|
||||
Hi
|
||||
---
|
||||
// Just page followed by pagebreak.
|
||||
// Should result in one forest-colored A11 page and one auto-sized page.
|
||||
#page("a11", flipped: true, fill: forest)[]
|
||||
#pagebreak()
|
||||
|
@ -1,35 +1,23 @@
|
||||
// Test forced page breaks.
|
||||
|
||||
---
|
||||
First of two
|
||||
// Just a pagebreak.
|
||||
// Should result in two auto-sized pages.
|
||||
#pagebreak()
|
||||
#set page(height: 40pt)
|
||||
Second of two
|
||||
|
||||
---
|
||||
// Make sure that you can't do page related stuff in a container.
|
||||
A
|
||||
#box[
|
||||
B
|
||||
#pagebreak()
|
||||
#set page("a4")
|
||||
]
|
||||
C
|
||||
|
||||
// No consequences from the page("A4") call here.
|
||||
// Pagebreak, empty with styles and then pagebreak
|
||||
// Should result in one auto-sized page and two conifer-colored A11 pages.
|
||||
#pagebreak()
|
||||
#set page(width: 2cm, fill: conifer)
|
||||
#pagebreak()
|
||||
D
|
||||
|
||||
---
|
||||
// Test a combination of pages with bodies and normal content.
|
||||
|
||||
// Test a combination of pagebreaks, styled pages and pages with bodies.
|
||||
#set page(width: 80pt, height: 30pt)
|
||||
|
||||
[#set page(width: 80pt); First]
|
||||
[#set page(width: 60pt); First]
|
||||
#pagebreak()
|
||||
#pagebreak()
|
||||
#pagebreak()
|
||||
Fourth
|
||||
#page(height: 20pt)[]
|
||||
Sixth
|
||||
[#set page(); Seventh]
|
||||
Third
|
||||
#page(height: 20pt, fill: forest)[]
|
||||
Fif[#set page();th]
|
||||
|
@ -1,38 +1,27 @@
|
||||
// Test the `h` and `v` functions.
|
||||
|
||||
---
|
||||
// Ends paragraphs.
|
||||
Tightly #v(0pt) packed
|
||||
// Linebreak and v(0pt) are equivalent.
|
||||
#box[A \ B] #box[A #v(0pt) B]
|
||||
|
||||
// Eating up soft spacing.
|
||||
Inv #h(0pt) isible
|
||||
Inv#h(0pt)isible
|
||||
|
||||
// Multiple spacings in a row.
|
||||
Add #h(10pt) #h(10pt) up
|
||||
|
||||
// Relative to area.
|
||||
#let x = 25% - 4pt
|
||||
| #h(x) | #h(x) | #h(x) | #h(x) |
|
||||
|#h(x)|#h(x)|#h(x)|#h(x)|
|
||||
|
||||
// Fractional.
|
||||
| #h(1fr) | #h(2fr) | #h(1fr) |
|
||||
|
||||
---
|
||||
// Test spacing collapsing with parbreaks.
|
||||
#v(0pt)
|
||||
A
|
||||
#v(0pt)
|
||||
B
|
||||
#v(0pt)
|
||||
|
||||
C #parbreak() D
|
||||
|
||||
---
|
||||
// Test that spacing can carry paragraph and page style properties.
|
||||
|
||||
A[#set par(align: right);#h(1cm)]B
|
||||
[#set page(height: 20pt);#v(1cm)]
|
||||
B
|
||||
// Test spacing collapsing before spacing.
|
||||
#set par(align: right)
|
||||
A #h(0pt) B #h(0pt) \
|
||||
A B
|
||||
|
||||
---
|
||||
// Missing spacing.
|
||||
|
@ -31,7 +31,7 @@
|
||||
---
|
||||
// Test spacing.
|
||||
#set page(width: 50pt, margins: 0pt)
|
||||
#set par(spacing: 5pt)
|
||||
#set par(leading: 5pt)
|
||||
|
||||
#let x = square(size: 10pt, fill: eastern)
|
||||
#stack(dir: rtl, spacing: 5pt, x, x, x)
|
||||
|
@ -8,6 +8,7 @@
|
||||
---
|
||||
1. First.
|
||||
2. Second.
|
||||
|
||||
1. Back to first.
|
||||
|
||||
---
|
||||
|
@ -14,13 +14,15 @@ paragraphs.
|
||||
|
||||
---
|
||||
- First level.
|
||||
|
||||
- Second level.
|
||||
|
||||
There are multiple paragraphs.
|
||||
- Third level.
|
||||
|
||||
Still the same bullet point.
|
||||
|
||||
- Still level 2.
|
||||
|
||||
- At the top.
|
||||
|
||||
---
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
---
|
||||
// Ensure that constructor styles aren't passed down the tree.
|
||||
#set par(spacing: 2pt)
|
||||
#set par(leading: 2pt)
|
||||
#list(
|
||||
body-indent: 20pt,
|
||||
[First],
|
||||
|
@ -8,7 +8,6 @@ Hello *{x}*
|
||||
|
||||
---
|
||||
// Test that lists are affected by correct indents.
|
||||
#set par(spacing: 4pt)
|
||||
#let fruit = [
|
||||
- Apple
|
||||
- Orange
|
||||
@ -23,7 +22,7 @@ Hello *{x}*
|
||||
---
|
||||
// Test that that par spacing and text style are respected from
|
||||
// the outside, but the more specific fill is respected.
|
||||
#set par(spacing: 4pt)
|
||||
#set par(spacing: 0pt)
|
||||
#set text(style: "italic", fill: eastern)
|
||||
#let x = [And the forest #parbreak() lay silent!]
|
||||
#text(fill: forest, x)
|
||||
|
@ -7,11 +7,11 @@ To the right! Where the sunlight peeks behind the mountain.
|
||||
|
||||
---
|
||||
// Test that explicit paragraph break respects active styles.
|
||||
#set par(spacing: 7pt)
|
||||
#set par(spacing: 0pt)
|
||||
[#set par(spacing: 100pt);First]
|
||||
|
||||
[#set par(spacing: 100pt);Second]
|
||||
#set par(spacing: 20pt)
|
||||
#set par(spacing: 13.5pt)
|
||||
|
||||
Third
|
||||
|
||||
@ -21,32 +21,26 @@ Hello
|
||||
|
||||
#set par(spacing: 100pt)
|
||||
World
|
||||
#set par(spacing: 0pt)
|
||||
#set par(spacing: 0pt, leading: 0pt)
|
||||
|
||||
You
|
||||
|
||||
---
|
||||
// Test that paragraphs break due to incompatibility has correct spacing.
|
||||
A #set par(spacing: 0pt); B #parbreak() C
|
||||
A #set par(spacing: 0pt, leading: 0pt); B #parbreak() C
|
||||
|
||||
---
|
||||
// Test that paragraph breaks due to block nodes have the correct spacing.
|
||||
#set par(spacing: 10pt)
|
||||
- A
|
||||
|
||||
#set par(spacing: 0pt)
|
||||
#set par(leading: 0pt)
|
||||
- B
|
||||
- C
|
||||
#set par(spacing: 5pt)
|
||||
#set par(leading: 5pt)
|
||||
- D
|
||||
- E
|
||||
|
||||
---
|
||||
// Test that paragraph break due to incompatibility respects
|
||||
// spacing defined by the two adjacent paragraphs.
|
||||
#let a = [#set par(spacing: 40pt);Hello]
|
||||
#let b = [#set par(spacing: 10pt);World]
|
||||
{a}{b}
|
||||
|
||||
---
|
||||
// Test weird metrics.
|
||||
#set par(spacing: 100%, leading: 0pt)
|
||||
|