Redesigned template layout

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

7
Cargo.lock generated
View File

@ -932,6 +932,12 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
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",

View File

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

@ -0,0 +1,109 @@
use super::{StyleChain, StyleVec, StyleVecBuilder};
/// A wrapper around a [`StyleVecBuilder`] that allows to collapse items.
pub struct CollapsingBuilder<'a, T> {
builder: StyleVecBuilder<'a, T>,
staged: Vec<(T, StyleChain<'a>, bool)>,
last: Last,
}
/// What the last non-ignorant item was.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum Last {
Weak,
Destructive,
Supportive,
}
impl<'a, T: Merge> CollapsingBuilder<'a, T> {
/// Create a new style-vec builder.
pub fn new() -> Self {
Self {
builder: StyleVecBuilder::new(),
staged: vec![],
last: Last::Destructive,
}
}
/// Can only exist when there is at least one supportive item to its left
/// and to its right, with no destructive items or weak items in between to
/// its left and no destructive items in between to its right. There may be
/// ignorant items in between in both directions.
pub fn weak(&mut self, item: T, styles: StyleChain<'a>) {
if self.last == Last::Supportive {
self.staged.push((item, styles, true));
self.last = Last::Weak;
}
}
/// Forces nearby weak items to collapse.
pub fn destructive(&mut self, item: T, styles: StyleChain<'a>) {
self.flush(false);
self.push(item, styles);
self.last = Last::Destructive;
}
/// Allows nearby weak items to exist.
pub fn supportive(&mut self, item: T, styles: StyleChain<'a>) {
self.flush(true);
self.push(item, styles);
self.last = Last::Supportive;
}
/// Has no influence on other items.
pub fn ignorant(&mut self, item: T, styles: StyleChain<'a>) {
self.staged.push((item, styles, false));
}
/// Return the finish style vec and the common prefix chain.
pub fn finish(mut self) -> (StyleVec<T>, StyleChain<'a>) {
self.flush(false);
self.builder.finish()
}
/// Push the staged items, filtering out weak items if `supportive` is false.
fn flush(&mut self, supportive: bool) {
for (item, styles, weak) in self.staged.drain(..) {
if !weak || supportive {
push_merging(&mut self.builder, item, styles);
}
}
}
/// Push a new item into the style vector.
fn push(&mut self, item: T, styles: StyleChain<'a>) {
push_merging(&mut self.builder, item, styles);
}
}
/// Push an item into a style-vec builder, trying to merging it with the
/// previous item.
fn push_merging<'a, T: Merge>(
builder: &mut StyleVecBuilder<'a, T>,
item: T,
styles: StyleChain<'a>,
) {
if let Some((prev_item, prev_styles)) = builder.last_mut() {
if styles == prev_styles {
if prev_item.merge(&item) {
return;
}
}
}
builder.push(item, styles);
}
impl<'a, T: Merge> Default for CollapsingBuilder<'a, T> {
fn default() -> Self {
Self::new()
}
}
/// Defines if and how to merge two adjacent items in a [`CollapsingBuilder`].
pub trait Merge {
/// Try to merge the items, returning whether they were merged.
///
/// Defaults to not merging.
fn merge(&mut self, next: &Self) -> bool;
}

View File

@ -10,6 +10,7 @@ mod value;
mod styles;
mod 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::*;

View File

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

View File

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

View File

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

View File

@ -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::*;

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 756 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -6,11 +6,10 @@ Sekretariat MA \
Dr. Max Mustermann \
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%))

View File

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

View File

@ -0,0 +1,14 @@
// Test that you can't do page related stuff in a container.
---
A
#box[
B
#pagebreak()
#set page("a4")
]
C
// No consequences from the page("A4") call here.
#pagebreak()
D

View File

@ -0,0 +1,20 @@
// Test page margins.
---
// Set all margins at once.
[
#set page(height: 20pt, margins: 5pt)
#place(top + left)[TL]
#place(bottom + right)[BR]
]
---
// Set individual margins.
#set page(height: 40pt)
[#set page(left: 0pt); #align(left)[Left]]
[#set page(right: 0pt); #align(right)[Right]]
[#set page(top: 0pt); #align(top)[Top]]
[#set page(bottom: 0pt); #align(bottom)[Bottom]]
// Ensure that specific margins override general margins.
[#set page(margins: 0pt, left: 20pt); Overriden]

View File

@ -0,0 +1,28 @@
// Test setting page styles.
---
// Empty with styles
// Should result in one conifer-colored A11 page.
#set page("a11", flipped: true, fill: conifer)
---
// Empty with styles and then pagebreak
// Should result in two forest-colored pages.
#set page(fill: forest)
#pagebreak()
---
// Empty with multiple page styles.
// Should result in a small white page.
#set page("a4")
#set page("a5")
#set page(width: 1cm, height: 1cm)
---
// Empty with multiple page styles.
// Should result in one eastern-colored A11 page.
#set page("a4")
#set page("a5")
#set page("a11", flipped: true, fill: eastern)
#set text("Roboto", white, smallcaps: true)
Typst

View File

@ -1,34 +1,33 @@
// Test configuring page sizes and margins.
// Test the page class.
---
// Just empty page.
// Should result in auto-sized page, just like nothing.
#page[]
---
// Just empty page with styles.
// Should result in one conifer-colored A11 page.
#page("a11", flipped: true, fill: conifer)[]
---
// Set width and height.
// 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()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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