diff --git a/library/src/ext.rs b/library/src/ext.rs index 70b69dce..0735dc18 100644 --- a/library/src/ext.rs +++ b/library/src/ext.rs @@ -12,9 +12,6 @@ pub trait ContentExt { /// Underline this content. fn underlined(self) -> Self; - /// Add weak vertical spacing above and below the content. - fn spaced(self, above: Option, below: Option) -> Self; - /// Force a size for this content. fn boxed(self, sizing: Axes>>) -> Self; @@ -47,30 +44,6 @@ impl ContentExt for Content { text::DecoNode::<{ text::UNDERLINE }>(self).pack() } - fn spaced(self, above: Option, below: Option) -> Self { - if above.is_none() && below.is_none() { - return self; - } - - let mut seq = vec![]; - if let Some(above) = above { - seq.push( - layout::VNode { amount: above.into(), weak: true, generated: true } - .pack(), - ); - } - - seq.push(self); - if let Some(below) = below { - seq.push( - layout::VNode { amount: below.into(), weak: true, generated: true } - .pack(), - ); - } - - Content::sequence(seq) - } - fn boxed(self, sizing: Axes>>) -> Self { layout::BoxNode { sizing, child: self }.pack() } diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs index d65b78b6..22a9e02e 100644 --- a/library/src/layout/container.rs +++ b/library/src/layout/container.rs @@ -1,3 +1,4 @@ +use super::VNode; use crate::prelude::*; /// An inline-level container that sizes content. @@ -63,9 +64,22 @@ pub struct BlockNode(pub Content); #[node(LayoutBlock)] impl BlockNode { + /// The spacing between the previous and this block. + #[property(skip)] + pub const ABOVE: VNode = VNode::weak(Em::new(1.2).into()); + /// The spacing between this and the following block. + #[property(skip)] + pub const BELOW: VNode = VNode::weak(Em::new(1.2).into()); + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Self(args.eat()?.unwrap_or_default()).pack()) } + + fn set(...) { + let spacing = args.named("spacing")?.map(VNode::weak); + styles.set_opt(Self::ABOVE, args.named("above")?.map(VNode::strong).or(spacing)); + styles.set_opt(Self::BELOW, args.named("below")?.map(VNode::strong).or(spacing)); + } } impl LayoutBlock for BlockNode { diff --git a/library/src/layout/flow.rs b/library/src/layout/flow.rs index 347c1dd8..cc5dcd50 100644 --- a/library/src/layout/flow.rs +++ b/library/src/layout/flow.rs @@ -1,6 +1,7 @@ use std::cmp::Ordering; -use super::{AlignNode, PlaceNode, Spacing}; +use super::{AlignNode, PlaceNode, Spacing, VNode}; +use crate::layout::BlockNode; use crate::prelude::*; use crate::text::ParNode; @@ -15,7 +16,7 @@ pub struct FlowNode(pub StyleVec); #[derive(Hash, PartialEq)] pub enum FlowChild { /// Vertical spacing between other children. - Spacing(Spacing), + Spacing(VNode), /// Arbitrary block-level content. Block(Content), /// A column / region break. @@ -32,13 +33,13 @@ impl LayoutBlock for FlowNode { regions: &Regions, styles: StyleChain, ) -> SourceResult> { - let mut layouter = FlowLayouter::new(regions); + let mut layouter = FlowLayouter::new(regions, styles); for (child, map) in self.0.iter() { let styles = map.chain(&styles); match child { - FlowChild::Spacing(kind) => { - layouter.layout_spacing(*kind, styles); + FlowChild::Spacing(node) => { + layouter.layout_spacing(node, styles); } FlowChild::Block(block) => { layouter.layout_block(world, block, styles)?; @@ -80,9 +81,11 @@ impl PartialOrd for FlowChild { } /// Performs flow layout. -struct FlowLayouter { +struct FlowLayouter<'a> { /// The regions to layout children into. regions: Regions, + /// The shared styles. + shared: StyleChain<'a>, /// Whether the flow should expand to fill the region. expand: Axes, /// The full size of `regions.size` that was available before we started @@ -92,6 +95,8 @@ struct FlowLayouter { used: Size, /// The sum of fractions in the current region. fr: Fr, + /// The spacing below the last block. + below: Option, /// Spacing and layouted blocks. items: Vec, /// Finished frames for previous regions. @@ -110,9 +115,9 @@ enum FlowItem { Placed(Frame), } -impl FlowLayouter { +impl<'a> FlowLayouter<'a> { /// Create a new flow layouter. - fn new(regions: &Regions) -> Self { + fn new(regions: &Regions, shared: StyleChain<'a>) -> Self { let expand = regions.expand; let full = regions.first; @@ -122,18 +127,20 @@ impl FlowLayouter { Self { regions, + shared, expand, full, used: Size::zero(), fr: Fr::zero(), + below: None, items: vec![], finished: vec![], } } /// Layout spacing. - fn layout_spacing(&mut self, spacing: Spacing, styles: StyleChain) { - match spacing { + fn layout_spacing(&mut self, node: &VNode, styles: StyleChain) { + match node.amount { Spacing::Relative(v) => { // Resolve the spacing and limit it to the remaining space. let resolved = v.resolve(styles).relative_to(self.full.y); @@ -147,6 +154,10 @@ impl FlowLayouter { self.fr += v; } } + + if node.weak || node.amount.is_fractional() { + self.below = None; + } } /// Layout a block. @@ -161,9 +172,19 @@ impl FlowLayouter { self.finish_region(); } + // Add spacing between the last block and this one. + if let Some(below) = self.below.take() { + let above = styles.get(BlockNode::ABOVE); + let pick_below = (above.weak && !below.weak) || (below.amount > above.amount); + let spacing = if pick_below { below } else { above }; + self.layout_spacing(&spacing, self.shared); + } + // Placed nodes that are out of flow produce placed items which aren't // aligned later. + let mut is_placed = false; if let Some(placed) = block.downcast::() { + is_placed = true; if placed.out_of_flow() { let frame = block.layout_block(world, &self.regions, styles)?.remove(0); self.items.push(FlowItem::Placed(frame)); @@ -202,6 +223,10 @@ impl FlowLayouter { } } + if !is_placed { + self.below = Some(styles.get(BlockNode::BELOW)); + } + Ok(()) } @@ -250,6 +275,7 @@ impl FlowLayouter { self.full = self.regions.first; self.used = Size::zero(); self.fr = Fr::zero(); + self.below = None; self.finished.push(output); } diff --git a/library/src/layout/grid.rs b/library/src/layout/grid.rs index 8e1cb7a2..8af69b9a 100644 --- a/library/src/layout/grid.rs +++ b/library/src/layout/grid.rs @@ -1,5 +1,7 @@ use crate::prelude::*; +use super::Spacing; + /// Arrange content in a grid. #[derive(Debug, Hash)] pub struct GridNode { @@ -66,6 +68,15 @@ pub enum TrackSizing { Fractional(Fr), } +impl From for TrackSizing { + fn from(spacing: Spacing) -> Self { + match spacing { + Spacing::Relative(rel) => Self::Relative(rel), + Spacing::Fractional(fr) => Self::Fractional(fr), + } + } +} + /// Track sizing definitions. #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] pub struct TrackSizings(pub Vec); diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs index 1032ba54..e0eb431c 100644 --- a/library/src/layout/mod.rs +++ b/library/src/layout/mod.rs @@ -32,8 +32,8 @@ use typst::diag::SourceResult; use typst::frame::Frame; use typst::geom::*; use typst::model::{ - capability, Barrier, Content, Node, SequenceNode, Show, StyleChain, StyleEntry, - StyleVec, StyleVecBuilder, StyledNode, Target, + capability, Content, Node, SequenceNode, Show, StyleChain, StyleEntry, StyleVec, + StyleVecBuilder, StyledNode, Target, }; use typst::World; @@ -85,10 +85,12 @@ impl LayoutBlock for Content { regions: &Regions, styles: StyleChain, ) -> SourceResult> { - if let Some(node) = self.to::() { - let barrier = StyleEntry::Barrier(Barrier::new(self.id())); - let styles = barrier.chain(&styles); - return node.layout_block(world, regions, styles); + if !self.has::() || !styles.applicable(self) { + if let Some(node) = self.to::() { + let barrier = StyleEntry::Barrier(self.id()); + let styles = barrier.chain(&styles); + return node.layout_block(world, regions, styles); + } } let scratch = Scratch::default(); @@ -119,16 +121,18 @@ impl LayoutInline for Content { regions: &Regions, styles: StyleChain, ) -> SourceResult> { - if let Some(node) = self.to::() { - let barrier = StyleEntry::Barrier(Barrier::new(self.id())); - let styles = barrier.chain(&styles); - return node.layout_inline(world, regions, styles); - } + if !self.has::() || !styles.applicable(self) { + if let Some(node) = self.to::() { + let barrier = StyleEntry::Barrier(self.id()); + let styles = barrier.chain(&styles); + return node.layout_inline(world, regions, styles); + } - if let Some(node) = self.to::() { - let barrier = StyleEntry::Barrier(Barrier::new(self.id())); - let styles = barrier.chain(&styles); - return node.layout_block(world, regions, styles); + if let Some(node) = self.to::() { + let barrier = StyleEntry::Barrier(self.id()); + let styles = barrier.chain(&styles); + return node.layout_block(world, regions, styles); + } } let scratch = Scratch::default(); @@ -253,8 +257,6 @@ struct Builder<'a> { struct Scratch<'a> { /// An arena where intermediate style chains are stored. styles: Arena>, - /// An arena for individual intermediate style entries. - entries: Arena, /// An arena where intermediate content resulting from show rules is stored. content: Arena, } @@ -354,18 +356,7 @@ impl<'a> Builder<'a> { Ok(()) } - fn show( - &mut self, - content: &'a Content, - styles: StyleChain<'a>, - ) -> SourceResult { - let barrier = StyleEntry::Barrier(Barrier::new(content.id())); - let styles = self - .scratch - .entries - .alloc(barrier) - .chain(self.scratch.styles.alloc(styles)); - + fn show(&mut self, content: &Content, styles: StyleChain<'a>) -> SourceResult { let Some(realized) = styles.apply(self.world, Target::Node(content))? else { return Ok(false); }; @@ -457,7 +448,7 @@ struct DocBuilder<'a> { } impl<'a> DocBuilder<'a> { - fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) { + fn accept(&mut self, content: &Content, styles: StyleChain<'a>) { if let Some(pagebreak) = content.downcast::() { self.keep_next = !pagebreak.weak; } @@ -477,10 +468,10 @@ impl Default for DocBuilder<'_> { /// Accepts flow content. #[derive(Default)] -struct FlowBuilder<'a>(CollapsingBuilder<'a, FlowChild>); +struct FlowBuilder<'a>(CollapsingBuilder<'a, FlowChild>, bool); impl<'a> FlowBuilder<'a> { - fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { + fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool { // Weak flow elements: // Weakness | Element // 0 | weak colbreak @@ -490,8 +481,11 @@ impl<'a> FlowBuilder<'a> { // 4 | generated weak fractional spacing // 5 | par spacing + let last_was_parbreak = self.1; + self.1 = false; + if content.is::() { - /* Nothing to do */ + self.1 = true; } else if let Some(colbreak) = content.downcast::() { if colbreak.weak { self.0.weak(FlowChild::Colbreak, styles, 0); @@ -499,10 +493,10 @@ impl<'a> FlowBuilder<'a> { self.0.destructive(FlowChild::Colbreak, styles); } } else if let Some(vertical) = content.downcast::() { - let child = FlowChild::Spacing(vertical.amount); + let child = FlowChild::Spacing(*vertical); let frac = vertical.amount.is_fractional(); if vertical.weak { - let weakness = 1 + u8::from(frac) + 2 * u8::from(vertical.generated); + let weakness = 1 + u8::from(frac); self.0.weak(child, styles, weakness); } else if frac { self.0.destructive(child, styles); @@ -510,6 +504,24 @@ impl<'a> FlowBuilder<'a> { self.0.ignorant(child, styles); } } else if content.has::() { + if !last_was_parbreak { + let tight = if let Some(node) = content.downcast::() { + node.tight + } else if let Some(node) = content.downcast::() { + node.tight + } else if let Some(node) = content.downcast::() { + node.tight + } else { + false + }; + + if tight { + let leading = styles.get(ParNode::LEADING); + let spacing = VNode::weak(leading.into()); + self.0.weak(FlowChild::Spacing(spacing), styles, 1); + } + } + let child = FlowChild::Block(content.clone()); if content.is::() { self.0.ignorant(child, styles); @@ -523,18 +535,6 @@ impl<'a> FlowBuilder<'a> { true } - fn par(&mut self, par: ParNode, styles: StyleChain<'a>, indent: bool) { - let amount = if indent && !styles.get(ParNode::SPACING_AND_INDENT) { - styles.get(ParNode::LEADING).into() - } else { - styles.get(ParNode::SPACING).into() - }; - - self.0.weak(FlowChild::Spacing(amount), styles, 5); - self.0.supportive(FlowChild::Block(par.pack()), styles); - self.0.weak(FlowChild::Spacing(amount), styles, 5); - } - fn finish(self, doc: &mut DocBuilder<'a>, styles: StyleChain<'a>) { let (flow, shared) = self.0.finish(); let styles = if flow.is_empty() { styles } else { shared }; @@ -552,7 +552,7 @@ impl<'a> FlowBuilder<'a> { struct ParBuilder<'a>(CollapsingBuilder<'a, ParChild>); impl<'a> ParBuilder<'a> { - fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { + fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool { // Weak par elements: // Weakness | Element // 0 | weak fractional spacing @@ -621,7 +621,7 @@ impl<'a> ParBuilder<'a> { children.push_front(ParChild::Spacing(indent.into())); } - parent.flow.par(ParNode(children), shared, !indent.is_zero()); + parent.flow.accept(&ParNode(children).pack(), shared); } fn is_empty(&self) -> bool { @@ -635,63 +635,54 @@ struct ListBuilder<'a> { items: StyleVecBuilder<'a, ListItem>, /// Whether the list contains no paragraph breaks. tight: bool, - /// Whether the list can be attached. - attachable: bool, /// Trailing content for which it is unclear whether it is part of the list. staged: Vec<(&'a Content, StyleChain<'a>)>, } impl<'a> ListBuilder<'a> { fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { - if self.items.is_empty() { - if content.is::() { - self.attachable = false; - } else if !content.is::() && !content.is::() { - self.attachable = true; - } - } - - if let Some(item) = content.downcast::() { + if !self.items.is_empty() + && (content.is::() || content.is::()) + { + self.staged.push((content, styles)); + } else if let Some(item) = content.downcast::() { if self .items .items() .next() - .map_or(true, |first| item.kind() == first.kind()) + .map_or(false, |first| item.kind() != first.kind()) { - self.items.push(item.clone(), styles); - self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::()); - return true; + return false; } - } else if !self.items.is_empty() - && (content.is::() || content.is::()) - { - self.staged.push((content, styles)); - return true; + + self.items.push(item.clone(), styles); + self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::()); + } else { + return false; } - false + true } fn finish(self, parent: &mut Builder<'a>) -> SourceResult<()> { let (items, shared) = self.items.finish(); + if let Some(item) = items.items().next() { + let tight = self.tight; + let content = match item.kind() { + LIST => ListNode:: { tight, items }.pack(), + ENUM => ListNode:: { tight, items }.pack(), + DESC | _ => ListNode:: { tight, items }.pack(), + }; - let Some(item) = items.items().next() else { return Ok(()) }; - let tight = self.tight; - let attached = tight && self.attachable; - let content = match item.kind() { - LIST => ListNode:: { tight, attached, items }.pack(), - ENUM => ListNode:: { tight, attached, items }.pack(), - DESC | _ => ListNode:: { tight, attached, items }.pack(), - }; - - let stored = parent.scratch.content.alloc(content); - parent.accept(stored, shared)?; + let stored = parent.scratch.content.alloc(content); + parent.accept(stored, shared)?; + } for (content, styles) in self.staged { parent.accept(content, styles)?; } - parent.list.attachable = true; + parent.list.tight = true; Ok(()) } @@ -706,7 +697,6 @@ impl Default for ListBuilder<'_> { Self { items: StyleVecBuilder::default(), tight: true, - attachable: true, staged: vec![], } } diff --git a/library/src/layout/spacing.rs b/library/src/layout/spacing.rs index 67fff5db..66d71ed1 100644 --- a/library/src/layout/spacing.rs +++ b/library/src/layout/spacing.rs @@ -1,10 +1,9 @@ use std::cmp::Ordering; use crate::prelude::*; -use crate::text::ParNode; /// Horizontal spacing. -#[derive(Debug, Clone, Hash)] +#[derive(Debug, Copy, Clone, Hash)] pub struct HNode { pub amount: Spacing, pub weak: bool, @@ -20,11 +19,22 @@ impl HNode { } /// Vertical spacing. -#[derive(Debug, Clone, Hash)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd)] pub struct VNode { pub amount: Spacing, pub weak: bool, - pub generated: bool, +} + +impl VNode { + /// Create weak vertical spacing. + pub fn weak(amount: Spacing) -> Self { + Self { amount, weak: true } + } + + /// Create strong vertical spacing. + pub fn strong(amount: Spacing) -> Self { + Self { amount, weak: false } + } } #[node] @@ -32,7 +42,7 @@ impl VNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let amount = args.expect("spacing")?; let weak = args.named("weak")?.unwrap_or(false); - Ok(Self { amount, weak, generated: false }.pack()) + Ok(Self { amount, weak }.pack()) } } @@ -59,6 +69,12 @@ impl From for Spacing { } } +impl From for Spacing { + fn from(em: Em) -> Self { + Self::Relative(Rel::new(Ratio::zero(), em.into())) + } +} + impl PartialOrd for Spacing { fn partial_cmp(&self, other: &Self) -> Option { match (self, other) { @@ -77,24 +93,3 @@ castable! { Value::Relative(v) => Self::Relative(v), Value::Fraction(v) => Self::Fractional(v), } - -/// Spacing around and between blocks, relative to paragraph spacing. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct BlockSpacing(Rel); - -castable!(BlockSpacing: Rel); - -impl Resolve for BlockSpacing { - type Output = Abs; - - fn resolve(self, styles: StyleChain) -> Self::Output { - let whole = styles.get(ParNode::SPACING); - self.0.resolve(styles).relative_to(whole) - } -} - -impl From for BlockSpacing { - fn from(ratio: Ratio) -> Self { - Self(ratio.into()) - } -} diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index ae3c4a9a..bcf09c04 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -5,9 +5,8 @@ mod tex; use std::fmt::Write; use self::tex::{layout_tex, Texify}; -use crate::layout::BlockSpacing; use crate::prelude::*; -use crate::text::{FallbackList, FontFamily, TextNode}; +use crate::text::FontFamily; /// A piece of a mathematical formula. #[derive(Debug, Clone, Hash)] @@ -18,15 +17,8 @@ pub struct MathNode { pub display: bool, } -#[node(Show, Finalize, LayoutInline, Texify)] +#[node(Show, LayoutInline, Texify)] impl MathNode { - /// The spacing above display math. - #[property(resolve, shorthand(around))] - pub const ABOVE: Option = Some(Ratio::one().into()); - /// The spacing below display math. - #[property(resolve, shorthand(around))] - pub const BELOW: Option = Some(Ratio::one().into()); - fn field(&self, name: &str) -> Option { match name { "display" => Some(Value::Bool(self.display)), @@ -40,27 +32,13 @@ impl Show for MathNode { self.clone().pack() } - fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { - Ok(self.clone().pack()) - } -} - -impl Finalize for MathNode { - fn finalize( - &self, - _: Tracked, - styles: StyleChain, - mut realized: Content, - ) -> SourceResult { - realized = realized.styled( - TextNode::FAMILY, - FallbackList(vec![FontFamily::new("NewComputerModernMath")]), - ); + fn show(&self, _: Tracked, styles: StyleChain) -> SourceResult { + let mut map = StyleMap::new(); + map.set_family(FontFamily::new("NewComputerModernMath"), styles); + let mut realized = self.clone().pack().styled_with_map(map); if self.display { - realized = realized - .aligned(Axes::with_x(Some(Align::Center.into()))) - .spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW)) + realized = realized.aligned(Axes::with_x(Some(Align::Center.into()))) } Ok(realized) diff --git a/library/src/structure/heading.rs b/library/src/structure/heading.rs index fe9b9013..87b522f7 100644 --- a/library/src/structure/heading.rs +++ b/library/src/structure/heading.rs @@ -1,6 +1,6 @@ use typst::font::FontWeight; -use crate::layout::{BlockNode, BlockSpacing}; +use crate::layout::{BlockNode, VNode}; use crate::prelude::*; use crate::text::{TextNode, TextSize}; @@ -16,25 +16,6 @@ pub struct HeadingNode { #[node(Show, Finalize)] impl HeadingNode { - /// Whether the heading appears in the outline. - pub const OUTLINED: bool = true; - /// Whether the heading is numbered. - pub const NUMBERED: bool = true; - - /// The spacing above the heading. - #[property(referenced, shorthand(around))] - pub const ABOVE: Leveled> = Leveled::Mapping(|level| { - let ratio = match level.get() { - 1 => 1.5, - _ => 1.2, - }; - Some(Ratio::new(ratio).into()) - }); - /// The spacing below the heading. - #[property(referenced, shorthand(around))] - pub const BELOW: Leveled> = - Leveled::Value(Some(Ratio::new(0.55).into())); - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Self { body: args.expect("body")?, @@ -65,76 +46,25 @@ impl Show for HeadingNode { impl Finalize for HeadingNode { fn finalize( &self, - world: Tracked, - styles: StyleChain, - mut realized: Content, + _: Tracked, + _: StyleChain, + realized: Content, ) -> SourceResult { - macro_rules! resolve { - ($key:expr) => { - styles.get($key).resolve(world, self.level)? - }; - } + let size = Em::new(match self.level.get() { + 1 => 1.4, + 2 => 1.2, + _ => 1.0, + }); + + let above = Em::new(if self.level.get() == 1 { 1.8 } else { 1.44 }); + let below = Em::new(0.66); let mut map = StyleMap::new(); - map.set(TextNode::SIZE, { - let size = match self.level.get() { - 1 => 1.4, - 2 => 1.2, - _ => 1.0, - }; - TextSize(Em::new(size).into()) - }); + map.set(TextNode::SIZE, TextSize(size.into())); map.set(TextNode::WEIGHT, FontWeight::BOLD); + map.set(BlockNode::ABOVE, VNode::strong(above.into())); + map.set(BlockNode::BELOW, VNode::strong(below.into())); - realized = realized.styled_with_map(map).spaced( - resolve!(Self::ABOVE).resolve(styles), - resolve!(Self::BELOW).resolve(styles), - ); - - Ok(realized) - } -} - -/// Either the value or a closure mapping to the value. -#[derive(Debug, Clone, PartialEq, Hash)] -pub enum Leveled { - /// A bare value. - Value(T), - /// A simple mapping from a heading level to a value. - Mapping(fn(NonZeroUsize) -> T), - /// A closure mapping from a heading level to a value. - Func(Func, Span), -} - -impl Leveled { - /// Resolve the value based on the level. - pub fn resolve( - &self, - world: Tracked, - level: NonZeroUsize, - ) -> SourceResult { - Ok(match self { - Self::Value(value) => value.clone(), - Self::Mapping(mapping) => mapping(level), - Self::Func(func, span) => { - let args = Args::new(*span, [Value::Int(level.get() as i64)]); - func.call_detached(world, args)?.cast().at(*span)? - } - }) - } -} - -impl Cast> for Leveled { - fn is(value: &Spanned) -> bool { - matches!(&value.v, Value::Func(_)) || T::is(&value.v) - } - - fn cast(value: Spanned) -> StrResult { - match value.v { - Value::Func(v) => Ok(Self::Func(v, value.span)), - v => T::cast(v) - .map(Self::Value) - .map_err(|msg| with_alternative(msg, "function")), - } + Ok(realized.styled_with_map(map)) } } diff --git a/library/src/structure/list.rs b/library/src/structure/list.rs index 89dc0f35..8de22f64 100644 --- a/library/src/structure/list.rs +++ b/library/src/structure/list.rs @@ -1,17 +1,15 @@ use unscanny::Scanner; use crate::base::Numbering; -use crate::layout::{BlockSpacing, GridNode, HNode, TrackSizing}; +use crate::layout::{BlockNode, GridNode, HNode, Spacing, TrackSizing}; use crate::prelude::*; use crate::text::{ParNode, SpaceNode, TextNode}; /// An unordered (bulleted) or ordered (numbered) list. -#[derive(Debug, Hash)] +#[derive(Debug, Clone, Hash)] pub struct ListNode { /// If true, the items are separated by leading instead of list spacing. pub tight: bool, - /// If true, the spacing above the list is leading instead of above spacing. - pub attached: bool, /// The individual bulleted or numbered items. pub items: StyleVec, } @@ -22,7 +20,7 @@ pub type EnumNode = ListNode; /// A description list. pub type DescNode = ListNode; -#[node(Show, Finalize)] +#[node(Show, LayoutBlock)] impl ListNode { /// How the list is labelled. #[property(referenced)] @@ -37,16 +35,8 @@ impl ListNode { DESC | _ => 1.0, }) .into(); - - /// The spacing above the list. - #[property(resolve, shorthand(around))] - pub const ABOVE: Option = Some(Ratio::one().into()); - /// The spacing below the list. - #[property(resolve, shorthand(around))] - pub const BELOW: Option = Some(Ratio::one().into()); /// The spacing between the items of a wide (non-tight) list. - #[property(resolve)] - pub const SPACING: BlockSpacing = Ratio::one().into(); + pub const SPACING: Smart = Smart::Auto; fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let items = match L { @@ -73,18 +63,12 @@ impl ListNode { .collect(), }; - Ok(Self { - tight: args.named("tight")?.unwrap_or(true), - attached: args.named("attached")?.unwrap_or(false), - items, - } - .pack()) + Ok(Self { tight: args.named("tight")?.unwrap_or(true), items }.pack()) } fn field(&self, name: &str) -> Option { match name { "tight" => Some(Value::Bool(self.tight)), - "attached" => Some(Value::Bool(self.attached)), "items" => { Some(Value::Array(self.items.items().map(|item| item.encode()).collect())) } @@ -102,11 +86,18 @@ impl Show for ListNode { .pack() } - fn show( + fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { + Ok(self.clone().pack()) + } +} + +impl LayoutBlock for ListNode { + fn layout_block( &self, world: Tracked, + regions: &Regions, styles: StyleChain, - ) -> SourceResult { + ) -> SourceResult> { let mut cells = vec![]; let mut number = 1; @@ -114,9 +105,11 @@ impl Show for ListNode { let indent = styles.get(Self::INDENT); let body_indent = styles.get(Self::BODY_INDENT); let gutter = if self.tight { - styles.get(ParNode::LEADING) + styles.get(ParNode::LEADING).into() } else { - styles.get(Self::SPACING) + styles + .get(Self::SPACING) + .unwrap_or_else(|| styles.get(BlockNode::BELOW).amount) }; for (item, map) in self.items.iter() { @@ -150,40 +143,17 @@ impl Show for ListNode { number += 1; } - Ok(GridNode { + GridNode { tracks: Axes::with_x(vec![ TrackSizing::Relative(indent.into()), TrackSizing::Auto, TrackSizing::Relative(body_indent.into()), TrackSizing::Auto, ]), - gutter: Axes::with_y(vec![TrackSizing::Relative(gutter.into())]), + gutter: Axes::with_y(vec![gutter.into()]), cells, } - .pack()) - } -} - -impl Finalize for ListNode { - fn finalize( - &self, - _: Tracked, - styles: StyleChain, - realized: Content, - ) -> SourceResult { - let mut above = styles.get(Self::ABOVE); - let mut below = styles.get(Self::BELOW); - - if self.attached { - if above.is_some() { - above = Some(styles.get(ParNode::LEADING)); - } - if below.is_some() { - below = Some(styles.get(ParNode::SPACING)); - } - } - - Ok(realized.spaced(above, below)) + .layout_block(world, regions, styles) } } diff --git a/library/src/structure/table.rs b/library/src/structure/table.rs index 8c6191be..72ea3da5 100644 --- a/library/src/structure/table.rs +++ b/library/src/structure/table.rs @@ -1,8 +1,8 @@ -use crate::layout::{BlockSpacing, GridNode, TrackSizing, TrackSizings}; +use crate::layout::{GridNode, TrackSizing, TrackSizings}; use crate::prelude::*; /// A table of items. -#[derive(Debug, Hash)] +#[derive(Debug, Clone, Hash)] pub struct TableNode { /// Defines sizing for content rows and columns. pub tracks: Axes>, @@ -12,7 +12,7 @@ pub struct TableNode { pub cells: Vec, } -#[node(Show, Finalize)] +#[node(Show, LayoutBlock)] impl TableNode { /// How to fill the cells. #[property(referenced)] @@ -23,13 +23,6 @@ impl TableNode { /// How much to pad the cells's content. pub const PADDING: Rel = Abs::pt(5.0).into(); - /// The spacing above the table. - #[property(resolve, shorthand(around))] - pub const ABOVE: Option = Some(Ratio::one().into()); - /// The spacing below the table. - #[property(resolve, shorthand(around))] - pub const BELOW: Option = Some(Ratio::one().into()); - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let TrackSizings(columns) = args.named("columns")?.unwrap_or_default(); let TrackSizings(rows) = args.named("rows")?.unwrap_or_default(); @@ -67,11 +60,18 @@ impl Show for TableNode { .pack() } - fn show( + fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { + Ok(self.clone().pack()) + } +} + +impl LayoutBlock for TableNode { + fn layout_block( &self, world: Tracked, + regions: &Regions, styles: StyleChain, - ) -> SourceResult { + ) -> SourceResult> { let fill = styles.get(Self::FILL); let stroke = styles.get(Self::STROKE).map(PartialStroke::unwrap_or_default); let padding = styles.get(Self::PADDING); @@ -99,23 +99,12 @@ impl Show for TableNode { }) .collect::>()?; - Ok(GridNode { + GridNode { tracks: self.tracks.clone(), gutter: self.gutter.clone(), cells, } - .pack()) - } -} - -impl Finalize for TableNode { - fn finalize( - &self, - _: Tracked, - styles: StyleChain, - realized: Content, - ) -> SourceResult { - Ok(realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW))) + .layout_block(world, regions, styles) } } diff --git a/library/src/text/par.rs b/library/src/text/par.rs index de948a98..0c4ec7af 100644 --- a/library/src/text/par.rs +++ b/library/src/text/par.rs @@ -28,18 +28,12 @@ pub enum ParChild { #[node(LayoutBlock)] impl ParNode { - /// The spacing between lines. - #[property(resolve)] - pub const LEADING: Length = Em::new(0.65).into(); - /// The extra spacing between paragraphs. - #[property(resolve)] - pub const SPACING: Length = Em::new(1.2).into(); /// The indent the first line of a consecutive paragraph should have. #[property(resolve)] pub const INDENT: Length = Length::zero(); - /// Whether to allow paragraph spacing when there is paragraph indent. - pub const SPACING_AND_INDENT: bool = false; - + /// The spacing between lines. + #[property(resolve)] + pub const LEADING: Length = Em::new(0.65).into(); /// How to align text and inline objects in their line. #[property(resolve)] pub const ALIGN: HorizontalAlign = HorizontalAlign(GenAlign::Start); diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index 2041b25e..d7dae244 100644 --- a/library/src/text/raw.rs +++ b/library/src/text/raw.rs @@ -6,8 +6,8 @@ use syntect::highlighting::{ use syntect::parsing::SyntaxSet; use typst::syntax; -use super::{FallbackList, FontFamily, Hyphenate, LinebreakNode, TextNode}; -use crate::layout::{BlockNode, BlockSpacing}; +use super::{FontFamily, Hyphenate, LinebreakNode, TextNode}; +use crate::layout::BlockNode; use crate::prelude::*; /// Monospaced text with optional syntax highlighting. @@ -19,17 +19,11 @@ pub struct RawNode { pub block: bool, } -#[node(Show, Finalize)] +#[node(Show)] impl RawNode { /// The language to syntax-highlight in. #[property(referenced)] pub const LANG: Option = None; - /// The spacing above block-level raw. - #[property(resolve, shorthand(around))] - pub const ABOVE: Option = Some(Ratio::one().into()); - /// The spacing below block-level raw. - #[property(resolve, shorthand(around))] - pub const BELOW: Option = Some(Ratio::one().into()); fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Self { @@ -104,31 +98,12 @@ impl Show for RawNode { map.set(TextNode::OVERHANG, false); map.set(TextNode::HYPHENATE, Hyphenate(Smart::Custom(false))); map.set(TextNode::SMART_QUOTES, false); + map.set_family(FontFamily::new("IBM Plex Mono"), styles); Ok(realized.styled_with_map(map)) } } -impl Finalize for RawNode { - fn finalize( - &self, - _: Tracked, - styles: StyleChain, - mut realized: Content, - ) -> SourceResult { - realized = realized.styled( - TextNode::FAMILY, - FallbackList(vec![FontFamily::new("IBM Plex Mono")]), - ); - - if self.block { - realized = realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW)); - } - - Ok(realized) - } -} - /// Style a piece of text with a syntect style. fn styled(piece: &str, foreground: Paint, style: Style) -> Content { let mut body = TextNode::packed(piece); diff --git a/src/model/styles.rs b/src/model/styles.rs index 62e3188f..98763c50 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -148,7 +148,7 @@ pub enum StyleEntry { /// A show rule recipe. Recipe(Recipe), /// A barrier for scoped styles. - Barrier(Barrier), + Barrier(NodeId), /// Guards against recursive show rules. Guard(RecipeId), /// Allows recursive show rules again. @@ -158,11 +158,11 @@ pub enum StyleEntry { impl StyleEntry { /// Make this style the first link of the `tail` chain. pub fn chain<'a>(&'a self, tail: &'a StyleChain) -> StyleChain<'a> { - if let StyleEntry::Barrier(barrier) = self { + if let StyleEntry::Barrier(id) = self { if !tail .entries() .filter_map(StyleEntry::property) - .any(|p| p.scoped() && barrier.is_for(p.node())) + .any(|p| p.scoped() && *id == p.node()) { return *tail; } @@ -203,7 +203,7 @@ impl Debug for StyleEntry { match self { Self::Property(property) => property.fmt(f)?, Self::Recipe(recipe) => recipe.fmt(f)?, - Self::Barrier(barrier) => barrier.fmt(f)?, + Self::Barrier(id) => write!(f, "Barrier for {id:?}")?, Self::Guard(sel) => write!(f, "Guard against {sel:?}")?, Self::Unguard(sel) => write!(f, "Unguard against {sel:?}")?, } @@ -246,6 +246,34 @@ impl<'a> StyleChain<'a> { K::get(self, self.values(key)) } + /// Whether the style chain has a matching recipe for the content. + pub fn applicable(self, content: &Content) -> bool { + let target = Target::Node(content); + + // Find out how many recipes there any and whether any of them match. + let mut n = 0; + let mut any = true; + for recipe in self.entries().filter_map(StyleEntry::recipe) { + n += 1; + any |= recipe.applicable(target); + } + + // Find an applicable recipe. + if any { + for recipe in self.entries().filter_map(StyleEntry::recipe) { + if recipe.applicable(target) { + let sel = RecipeId::Nth(n); + if !self.guarded(sel) { + return true; + } + } + n -= 1; + } + } + + false + } + /// Apply show recipes in this style chain to a target. pub fn apply( self, @@ -292,6 +320,7 @@ impl<'a> StyleChain<'a> { .to::() .unwrap() .show(world, self)?; + realized = Some(content.styled_with_entry(StyleEntry::Guard(sel))); } } @@ -342,8 +371,9 @@ impl<'a> StyleChain<'a> { fn values>(self, _: K) -> Values<'a, K> { Values { entries: self.entries(), - depth: 0, key: PhantomData, + barriers: 0, + guarded: false, } } @@ -379,8 +409,9 @@ impl PartialEq for StyleChain<'_> { /// An iterator over the values in a style chain. struct Values<'a, K> { entries: Entries<'a>, - depth: usize, key: PhantomData, + barriers: usize, + guarded: bool, } impl<'a, K: Key<'a>> Iterator for Values<'a, K> { @@ -391,13 +422,22 @@ impl<'a, K: Key<'a>> Iterator for Values<'a, K> { match entry { StyleEntry::Property(property) => { if let Some(value) = property.downcast::() { - if !property.scoped() || self.depth <= 1 { + if !property.scoped() + || if self.guarded { + self.barriers == 1 + } else { + self.barriers <= 1 + } + { return Some(value); } } } - StyleEntry::Barrier(barrier) => { - self.depth += barrier.is_for(K::node()) as usize; + StyleEntry::Barrier(id) => { + self.barriers += (*id == K::node()) as usize; + } + StyleEntry::Guard(RecipeId::Base(id)) => { + self.guarded |= *id == K::node(); } _ => {} } @@ -444,7 +484,7 @@ impl<'a> Iterator for Links<'a> { } /// A sequence of items with associated styles. -#[derive(Hash)] +#[derive(Clone, Hash)] pub struct StyleVec { items: Vec, maps: Vec<(StyleMap, usize)>, @@ -758,32 +798,6 @@ impl Debug for KeyId { } } -/// A scoped property barrier. -/// -/// Barriers interact with [scoped](super::StyleMap::scoped) styles: A scoped -/// style can still be read through a single barrier (the one of the node it -/// _should_ apply to), but a second barrier will make it invisible. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct Barrier(NodeId); - -impl Barrier { - /// Create a new barrier for the given node. - pub fn new(node: NodeId) -> Self { - Self(node) - } - - /// Whether this barrier is for the node `T`. - pub fn is_for(&self, node: NodeId) -> bool { - self.0 == node - } -} - -impl Debug for Barrier { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Barrier for {:?}", self.0) - } -} - /// A property that is resolved with other properties from the style chain. pub trait Resolve { /// The type of the resolved output. diff --git a/tests/ref/structure/attach.png b/tests/ref/structure/attach.png index 7082a475..9b0e4c68 100644 Binary files a/tests/ref/structure/attach.png and b/tests/ref/structure/attach.png differ diff --git a/tests/ref/structure/enum.png b/tests/ref/structure/enum.png index f58a6c85..39227b5b 100644 Binary files a/tests/ref/structure/enum.png and b/tests/ref/structure/enum.png differ diff --git a/tests/ref/style/construct.png b/tests/ref/style/construct.png index 97786177..43cf5d79 100644 Binary files a/tests/ref/style/construct.png and b/tests/ref/style/construct.png differ diff --git a/tests/ref/style/set.png b/tests/ref/style/set.png index 6e154980..94470239 100644 Binary files a/tests/ref/style/set.png and b/tests/ref/style/set.png differ diff --git a/tests/ref/style/show-recursive.png b/tests/ref/style/show-recursive.png index 2c9239b8..4c47a7af 100644 Binary files a/tests/ref/style/show-recursive.png and b/tests/ref/style/show-recursive.png differ diff --git a/tests/ref/text/indent.png b/tests/ref/text/indent.png index 2d7a2e38..deafc3f6 100644 Binary files a/tests/ref/text/indent.png and b/tests/ref/text/indent.png differ diff --git a/tests/ref/text/par.png b/tests/ref/text/par.png index 16622cc2..199c8919 100644 Binary files a/tests/ref/text/par.png and b/tests/ref/text/par.png differ diff --git a/tests/typ/base/eval.typ b/tests/typ/base/eval.typ index 668264d3..80235c0c 100644 --- a/tests/typ/base/eval.typ +++ b/tests/typ/base/eval.typ @@ -8,7 +8,6 @@ #eval("#let") --- -#set raw(around: none) #show raw: it => text("IBM Plex Sans", eval(it.text)) Interacting diff --git a/tests/typ/layout/stack-1.typ b/tests/typ/layout/stack-1.typ index 0ecbe246..6a884667 100644 --- a/tests/typ/layout/stack-1.typ +++ b/tests/typ/layout/stack-1.typ @@ -42,9 +42,9 @@ --- // Test aligning things in RTL stack with align function & fr units. #set page(width: 50pt, margins: 5pt) +#set block(spacing: 5pt) #set text(8pt) #stack(dir: rtl, 1fr, [A], 1fr, [B], [C]) -#v(5pt) #stack(dir: rtl, align(center, [A]), align(left, [B]), diff --git a/tests/typ/structure/attach.typ b/tests/typ/structure/attach.typ index c6d3c28c..c13de236 100644 --- a/tests/typ/structure/attach.typ +++ b/tests/typ/structure/attach.typ @@ -9,10 +9,8 @@ Attached to: Next paragraph. --- -// Test attached list without parbreak after it. -// Ensures the par spacing is used below by setting -// super high around spacing. -#set list(around: 100pt) +// Test that attached list isn't affected by block spacing. +#show list: it => { set block(above: 100pt); it } Hello - A World @@ -29,8 +27,8 @@ World - B --- -// Test not-attached tight list. -#set list(around: 15pt) +// Test non-attached tight list. +#set block(spacing: 15pt) Hello - A World @@ -41,8 +39,8 @@ World More. --- -// Test that wide lists cannot be attached ... -#set list(around: 15pt, spacing: 15pt) +// Test that wide lists cannot be ... +#set block(spacing: 15pt) Hello - A @@ -50,7 +48,7 @@ Hello World --- -// ... unless really forced to. +// ... even if forced to. Hello -#list(attached: true, tight: false)[A][B] +#list(tight: false)[A][B] World diff --git a/tests/typ/structure/desc.typ b/tests/typ/structure/desc.typ index af1b2986..1bc92625 100644 --- a/tests/typ/structure/desc.typ +++ b/tests/typ/structure/desc.typ @@ -30,8 +30,8 @@ No: list \ --- // Test style change. #set text(8pt) - / First list: #lorem(4) + #set desc(body-indent: 30pt) / Second list: #lorem(4) diff --git a/tests/typ/structure/heading.typ b/tests/typ/structure/heading.typ index b552f6c3..9fd4e648 100644 --- a/tests/typ/structure/heading.typ +++ b/tests/typ/structure/heading.typ @@ -43,9 +43,9 @@ multiline. --- // Test styling. -#show heading.where(level: 5): it => { +#show heading.where(level: 5): it => block( text(family: "Roboto", fill: eastern, it.body + [!]) -} +) = Heading ===== Heading 🌍 diff --git a/tests/typ/structure/list.typ b/tests/typ/structure/list.typ index 9a021f31..9ed5993a 100644 --- a/tests/typ/structure/list.typ +++ b/tests/typ/structure/list.typ @@ -6,7 +6,7 @@ No list --- _Shopping list_ -#list(attached: true)[Apples][Potatoes][Juice] +#list[Apples][Potatoes][Juice] --- - First level. diff --git a/tests/typ/style/construct.typ b/tests/typ/style/construct.typ index 4b0f966a..be11e97e 100644 --- a/tests/typ/style/construct.typ +++ b/tests/typ/style/construct.typ @@ -4,11 +4,7 @@ // Ensure that constructor styles aren't passed down the tree. // The inner list should have no extra indent. #set par(leading: 2pt) -#list( - body-indent: 20pt, - [First], - list([A], [B]) -) +#list(body-indent: 20pt, [First], list[A][B]) --- // Ensure that constructor styles win, but not over outer styles. @@ -29,5 +25,7 @@ A #rect(fill: yellow, inset: 5pt, rect()) B --- -// The inner list should not be indented extra. -[#set text(1em);#list(indent: 20pt, list[A])] +// The constructor property should still work +// when there are recursive show rules. +#show list: text.with(blue) +#list(label: "(a)", [A], list[B]) diff --git a/tests/typ/style/set.typ b/tests/typ/style/set.typ index 2864b81b..fc5053b1 100644 --- a/tests/typ/style/set.typ +++ b/tests/typ/style/set.typ @@ -19,9 +19,9 @@ Hello *{x}* - No more fruit --- -// Test that that par spacing and text style are respected from +// Test that that block spacing and text style are respected from // the outside, but the more specific fill is respected. -#set par(spacing: 4pt) +#set block(spacing: 4pt) #set text(style: "italic", fill: eastern) #let x = [And the forest #parbreak() lay silent!] #text(fill: forest, x) diff --git a/tests/typ/style/show-node.typ b/tests/typ/style/show-node.typ index 4b0542e5..51b4734e 100644 --- a/tests/typ/style/show-node.typ +++ b/tests/typ/style/show-node.typ @@ -2,7 +2,6 @@ --- // Override lists. -#set list(around: none) #show list: it => "(" + it.items.join(", ") + ")" - A @@ -13,7 +12,6 @@ --- // Test full reset. -#set heading(around: none) #show heading: [B] #show heading: text.with(size: 10pt, weight: 400) A [= Heading] C @@ -21,7 +19,6 @@ A [= Heading] C --- // Test full removal. #show heading: none -#set heading(around: none) Where is = There are no headings around here! @@ -29,7 +26,7 @@ my heading? --- // Test integrated example. -#show heading: it => { +#show heading: it => block({ set text(10pt) move(dy: -1pt)[📖] h(5pt) @@ -38,7 +35,7 @@ my heading? } else { text(red, it.body) } -} +}) = Task 1 Some text. diff --git a/tests/typ/style/show-recursive.typ b/tests/typ/style/show-recursive.typ index 566879af..91a295f2 100644 --- a/tests/typ/style/show-recursive.typ +++ b/tests/typ/style/show-recursive.typ @@ -15,52 +15,37 @@ - List = Nope ---- -// Test recursive base recipe. (Burn it with fire!) -#set list(label: [- Hey]) -- Labelless -- List - --- // Test show rule in function. -#let starwars(body) = [ - #show list: it => { +#let starwars(body) = { + show list: it => block({ stack(dir: ltr, text(red, it), 1fr, scale(x: -100%, text(blue, it)), ) - } - #body -] + }) + body +} - Normal list + #starwars[ - Star - Wars - List ] + - Normal list --- // Test multi-recursion with nested lists. -#set rect(inset: 2pt) +#set rect(inset: 3pt) #show list: rect.with(stroke: blue) #show list: rect.with(stroke: red) +#show list: block - List - Nested - List - Recursive! - ---- -// Inner heading is not finalized. Bug? -#set heading(around: none) -#show heading: it => it.body -#show heading: [ - = A [ - = B - ] -] - -= Discarded diff --git a/tests/typ/text/indent.typ b/tests/typ/text/indent.typ index 97044153..b2f3d87b 100644 --- a/tests/typ/text/indent.typ +++ b/tests/typ/text/indent.typ @@ -2,7 +2,7 @@ --- #set par(indent: 12pt, leading: 5pt) -#set heading(above: 8pt) +#set block(spacing: 5pt) #show heading: text.with(size: 10pt) The first paragraph has no indent. @@ -31,7 +31,7 @@ starts a paragraph without indent. --- // This is madness. -#set par(indent: 12pt, spacing-and-indent: true) +#set par(indent: 12pt) Why would anybody ever ... ... want spacing and indent? diff --git a/tests/typ/text/justify.typ b/tests/typ/text/justify.typ index e61817a6..e3d61322 100644 --- a/tests/typ/text/justify.typ +++ b/tests/typ/text/justify.typ @@ -1,12 +1,8 @@ --- #set page(width: 180pt) -#set par( - justify: true, - indent: 14pt, - spacing: 0pt, - leading: 5pt, -) +#set block(spacing: 5pt) +#set par(justify: true, indent: 14pt, leading: 5pt) This text is justified, meaning that spaces are stretched so that the text forms a "block" with flush edges at both sides. diff --git a/tests/typ/text/par.typ b/tests/typ/text/par.typ index 08202ef5..56a33577 100644 --- a/tests/typ/text/par.typ +++ b/tests/typ/text/par.typ @@ -7,33 +7,27 @@ To the right! Where the sunlight peeks behind the mountain. --- // Test changing leading and spacing. -#set par(spacing: 1em, leading: 2pt) +#set block(spacing: 1em) +#set par(leading: 2pt) But, soft! what light through yonder window breaks? It is the east, and Juliet is the sun. ---- -// Test that largest paragraph spacing wins. -#set par(spacing: 2.5pt) -[#set par(spacing: 15pt);First] -[#set par(spacing: 7.5pt);Second] -Third - -Fourth - --- // Test that paragraph spacing loses against block spacing. -#set par(spacing: 100pt) -#set table(around: 5pt) +// TODO +// #set block(spacing: 100pt) +// #show table: set block(spacing: 5pt) +#set block(spacing: 5pt) Hello #table(columns: 4, fill: (x, y) => if odd(x + y) { silver })[A][B][C][D] --- // While we're at it, test the larger block spacing wins. -#set raw(around: 15pt) -#set math(around: 7.5pt) -#set list(around: 2.5pt) -#set par(spacing: 0pt) +#set block(spacing: 0pt) +#show raw: it => { set block(spacing: 15pt); it } +#show math: it => { set block(spacing: 7.5pt); it } +#show list: it => { set block(spacing: 2.5pt); it } ```rust fn main() {}