summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-11-09 18:16:59 +0100
committerLaurenz <laurmaedje@gmail.com>2022-11-09 18:20:02 +0100
commit010cc2effc2fd0e1c4e52d5c914cb4d74506bc0a (patch)
treee50060d271f076b00945e5569e7f8ffef2c28e9f
parent12a59963b08b68cc39dcded4d3d3e6a6631c2732 (diff)
New block spacing model
-rw-r--r--library/src/ext.rs27
-rw-r--r--library/src/layout/container.rs14
-rw-r--r--library/src/layout/flow.rs46
-rw-r--r--library/src/layout/grid.rs11
-rw-r--r--library/src/layout/mod.rs156
-rw-r--r--library/src/layout/spacing.rs47
-rw-r--r--library/src/math/mod.rs36
-rw-r--r--library/src/structure/heading.rs102
-rw-r--r--library/src/structure/list.rs72
-rw-r--r--library/src/structure/table.rs39
-rw-r--r--library/src/text/par.rs12
-rw-r--r--library/src/text/raw.rs33
-rw-r--r--src/model/styles.rs86
-rw-r--r--tests/ref/structure/attach.pngbin10993 -> 11016 bytes
-rw-r--r--tests/ref/structure/enum.pngbin27743 -> 27684 bytes
-rw-r--r--tests/ref/style/construct.pngbin4651 -> 6024 bytes
-rw-r--r--tests/ref/style/set.pngbin24880 -> 24744 bytes
-rw-r--r--tests/ref/style/show-recursive.pngbin18572 -> 15653 bytes
-rw-r--r--tests/ref/text/indent.pngbin46427 -> 46495 bytes
-rw-r--r--tests/ref/text/par.pngbin29339 -> 25788 bytes
-rw-r--r--tests/typ/base/eval.typ1
-rw-r--r--tests/typ/layout/stack-1.typ2
-rw-r--r--tests/typ/structure/attach.typ18
-rw-r--r--tests/typ/structure/desc.typ2
-rw-r--r--tests/typ/structure/heading.typ4
-rw-r--r--tests/typ/structure/list.typ2
-rw-r--r--tests/typ/style/construct.typ12
-rw-r--r--tests/typ/style/set.typ4
-rw-r--r--tests/typ/style/show-node.typ7
-rw-r--r--tests/typ/style/show-recursive.typ33
-rw-r--r--tests/typ/text/indent.typ4
-rw-r--r--tests/typ/text/justify.typ8
-rw-r--r--tests/typ/text/par.typ26
33 files changed, 315 insertions, 489 deletions
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<Abs>, below: Option<Abs>) -> Self;
-
/// Force a size for this content.
fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self;
@@ -47,30 +44,6 @@ impl ContentExt for Content {
text::DecoNode::<{ text::UNDERLINE }>(self).pack()
}
- fn spaced(self, above: Option<Abs>, below: Option<Abs>) -> 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<Option<Rel<Length>>>) -> 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<Content> {
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<FlowChild>);
#[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<Vec<Frame>> {
- 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<bool>,
/// 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<VNode>,
/// Spacing and layouted blocks.
items: Vec<FlowItem>,
/// 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::<PlaceNode>() {
+ 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<Spacing> 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<TrackSizing>);
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<Vec<Frame>> {
- if let Some(node) = self.to::<dyn LayoutBlock>() {
- let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
- let styles = barrier.chain(&styles);
- return node.layout_block(world, regions, styles);
+ if !self.has::<dyn Show>() || !styles.applicable(self) {
+ if let Some(node) = self.to::<dyn LayoutBlock>() {
+ 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<Vec<Frame>> {
- if let Some(node) = self.to::<dyn LayoutInline>() {
- let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
- let styles = barrier.chain(&styles);
- return node.layout_inline(world, regions, styles);
- }
+ if !self.has::<dyn Show>() || !styles.applicable(self) {
+ if let Some(node) = self.to::<dyn LayoutInline>() {
+ let barrier = StyleEntry::Barrier(self.id());
+ let styles = barrier.chain(&styles);
+ return node.layout_inline(world, regions, styles);
+ }
- if let Some(node) = self.to::<dyn LayoutBlock>() {
- 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::<dyn LayoutBlock>() {
+ 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<StyleChain<'a>>,
- /// An arena for individual intermediate style entries.
- entries: Arena<StyleEntry>,
/// An arena where intermediate content resulting from show rules is stored.
content: Arena<Content>,
}
@@ -354,18 +356,7 @@ impl<'a> Builder<'a> {
Ok(())
}
- fn show(
- &mut self,
- content: &'a Content,
- styles: StyleChain<'a>,
- ) -> SourceResult<bool> {
- 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<bool> {
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::<PagebreakNode>() {
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::<ParbreakNode>() {
- /* Nothing to do */
+ self.1 = true;
} else if let Some(colbreak) = content.downcast::<ColbreakNode>() {
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::<VNode>() {
- 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::<dyn LayoutBlock>() {
+ if !last_was_parbreak {
+ let tight = if let Some(node) = content.downcast::<ListNode>() {
+ node.tight
+ } else if let Some(node) = content.downcast::<EnumNode>() {
+ node.tight
+ } else if let Some(node) = content.downcast::<DescNode>() {
+ 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::<PlaceNode>() {
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::<ParbreakNode>() {
- self.attachable = false;
- } else if !content.is::<SpaceNode>() && !content.is::<ListItem>() {
- self.attachable = true;
- }
- }
-
- if let Some(item) = content.downcast::<ListItem>() {
+ if !self.items.is_empty()
+ && (content.is::<SpaceNode>() || content.is::<ParbreakNode>())
+ {
+ self.staged.push((content, styles));
+ } else if let Some(item) = content.downcast::<ListItem>() {
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::<ParbreakNode>());
- return true;
+ return false;
}
- } else if !self.items.is_empty()
- && (content.is::<SpaceNode>() || content.is::<ParbreakNode>())
- {
- self.staged.push((content, styles));
- return true;
+
+ self.items.push(item.clone(), styles);
+ self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakNode>());
+ } 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::<LIST> { tight, items }.pack(),
+ ENUM => ListNode::<ENUM> { tight, items }.pack(),
+ DESC | _ => ListNode::<DESC> { 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::<LIST> { tight, attached, items }.pack(),
- ENUM => ListNode::<ENUM> { tight, attached, items }.pack(),
- DESC | _ => ListNode::<DESC> { 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<Content> {
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<Abs> for Spacing {
}
}
+impl From<Em> 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<Ordering> {
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<Length>);
-
-castable!(BlockSpacing: Rel<Length>);
-
-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<Ratio> 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<BlockSpacing> = Some(Ratio::one().into());
- /// The spacing below display math.
- #[property(resolve, shorthand(around))]
- pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
-
fn field(&self, name: &str) -> Option<Value> {
match name {
"display" => Some(Value::Bool(self.display)),
@@ -40,27 +32,13 @@ impl Show for MathNode {
self.clone().pack()
}
- fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
- Ok(self.clone().pack())
- }
-}
-
-impl Finalize for MathNode {
- fn finalize(
- &self,
- _: Tracked<dyn World>,
- styles: StyleChain,
- mut realized: Content,
- ) -> SourceResult<Content> {
- realized = realized.styled(
- TextNode::FAMILY,
- FallbackList(vec![FontFamily::new("NewComputerModernMath")]),
- );
+ fn show(&self, _: Tracked<dyn World>, styles: StyleChain) -> SourceResult<Content> {
+ 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<Option<BlockSpacing>> = 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<Option<BlockSpacing>> =
- Leveled::Value(Some(Ratio::new(0.55).into()));
-
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self {
body: args.expect("body")?,
@@ -65,76 +46,25 @@ impl Show for HeadingNode {
impl Finalize for HeadingNode {
fn finalize(
&self,
- world: Tracked<dyn World>,
- styles: StyleChain,
- mut realized: Content,
+ _: Tracked<dyn World>,
+ _: StyleChain,
+ realized: Content,
) -> SourceResult<Content> {
- macro_rules! resolve {
- ($key:expr) => {
- styles.get($key).resolve(world, self.level)?
- };
- }
-
- 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())
+ let size = Em::new(match self.level.get() {
+ 1 => 1.4,
+ 2 => 1.2,
+ _ => 1.0,
});
- map.set(TextNode::WEIGHT, FontWeight::BOLD);
- realized = realized.styled_with_map(map).spaced(
- resolve!(Self::ABOVE).resolve(styles),
- resolve!(Self::BELOW).resolve(styles),
- );
+ let above = Em::new(if self.level.get() == 1 { 1.8 } else { 1.44 });
+ let below = Em::new(0.66);
- Ok(realized)
- }
-}
-
-/// Either the value or a closure mapping to the value.
-#[derive(Debug, Clone, PartialEq, Hash)]
-pub enum Leveled<T> {
- /// 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<T: Cast + Clone> Leveled<T> {
- /// Resolve the value based on the level.
- pub fn resolve(
- &self,
- world: Tracked<dyn World>,
- level: NonZeroUsize,
- ) -> SourceResult<T> {
- 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<T: Cast> Cast<Spanned<Value>> for Leveled<T> {
- fn is(value: &Spanned<Value>) -> bool {
- matches!(&value.v, Value::Func(_)) || T::is(&value.v)
- }
+ let mut map = StyleMap::new();
+ 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()));
- fn cast(value: Spanned<Value>) -> StrResult<Self> {
- 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<const L: ListKind = LIST> {
/// 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<ListItem>,
}
@@ -22,7 +20,7 @@ pub type EnumNode = ListNode<ENUM>;
/// A description list.
pub type DescNode = ListNode<DESC>;
-#[node(Show, Finalize)]
+#[node(Show, LayoutBlock)]
impl<const L: ListKind> ListNode<L> {
/// How the list is labelled.
#[property(referenced)]
@@ -37,16 +35,8 @@ impl<const L: ListKind> ListNode<L> {
DESC | _ => 1.0,
})
.into();
-
- /// The spacing above the list.
- #[property(resolve, shorthand(around))]
- pub const ABOVE: Option<BlockSpacing> = Some(Ratio::one().into());
- /// The spacing below the list.
- #[property(resolve, shorthand(around))]
- pub const BELOW: Option<BlockSpacing> = 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<Spacing> = Smart::Auto;
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let items = match L {
@@ -73,18 +63,12 @@ impl<const L: ListKind> ListNode<L> {
.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<Value> {
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<const L: ListKind> Show for ListNode<L> {
.pack()
}
- fn show(
+ fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
+ Ok(self.clone().pack())
+ }
+}
+
+impl<const L: ListKind> LayoutBlock for ListNode<L> {
+ fn layout_block(
&self,
world: Tracked<dyn World>,
+ regions: &Regions,
styles: StyleChain,
- ) -> SourceResult<Content> {
+ ) -> SourceResult<Vec<Frame>> {
let mut cells = vec![];
let mut number = 1;
@@ -114,9 +105,11 @@ impl<const L: ListKind> Show for ListNode<L> {
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<const L: ListKind> Show for ListNode<L> {
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<const L: ListKind> Finalize for ListNode<L> {
- fn finalize(
- &self,
- _: Tracked<dyn World>,
- styles: StyleChain,
- realized: Content,
- ) -> SourceResult<Content> {
- 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<Vec<TrackSizing>>,
@@ -12,7 +12,7 @@ pub struct TableNode {
pub cells: Vec<Content>,
}
-#[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<Length> = Abs::pt(5.0).into();
- /// The spacing above the table.
- #[property(resolve, shorthand(around))]
- pub const ABOVE: Option<BlockSpacing> = Some(Ratio::one().into());
- /// The spacing below the table.
- #[property(resolve, shorthand(around))]
- pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
-
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
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<dyn World>, _: StyleChain) -> SourceResult<Content> {
+ Ok(self.clone().pack())
+ }
+}
+
+impl LayoutBlock for TableNode {
+ fn layout_block(
&self,
world: Tracked<dyn World>,
+ regions: &Regions,
styles: StyleChain,
- ) -> SourceResult<Content> {
+ ) -> SourceResult<Vec<Frame>> {
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::<SourceResult<_>>()?;
- Ok(GridNode {
+ GridNode {
tracks: self.tracks.clone(),
gutter: self.gutter.clone(),
cells,
}
- .pack())
- }
-}
-
-impl Finalize for TableNode {
- fn finalize(
- &self,
- _: Tracked<dyn World>,
- styles: StyleChain,
- realized: Content,
- ) -> SourceResult<Content> {
- 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<EcoString> = None;
- /// The spacing above block-level raw.
- #[property(resolve, shorthand(around))]
- pub const ABOVE: Option<BlockSpacing> = Some(Ratio::one().into());
- /// The spacing below block-level raw.
- #[property(resolve, shorthand(around))]
- pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
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<dyn World>,
- styles: StyleChain,
- mut realized: Content,
- ) -> SourceResult<Content> {
- 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::<dyn Show>()
.unwrap()
.show(world, self)?;
+
realized = Some(content.styled_with_entry(StyleEntry::Guard(sel)));
}
}
@@ -342,8 +371,9 @@ impl<'a> StyleChain<'a> {
fn values<K: Key<'a>>(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<K>,
+ 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::<K>() {
- 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<T> {
items: Vec<T>,
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
--- a/tests/ref/structure/attach.png
+++ b/tests/ref/structure/attach.png
Binary files differ
diff --git a/tests/ref/structure/enum.png b/tests/ref/structure/enum.png
index f58a6c85..39227b5b 100644
--- a/tests/ref/structure/enum.png
+++ b/tests/ref/structure/enum.png
Binary files differ
diff --git a/tests/ref/style/construct.png b/tests/ref/style/construct.png
index 97786177..43cf5d79 100644
--- a/tests/ref/style/construct.png
+++ b/tests/ref/style/construct.png
Binary files differ
diff --git a/tests/ref/style/set.png b/tests/ref/style/set.png
index 6e154980..94470239 100644
--- a/tests/ref/style/set.png
+++ b/tests/ref/style/set.png
Binary files differ
diff --git a/tests/ref/style/show-recursive.png b/tests/ref/style/show-recursive.png
index 2c9239b8..4c47a7af 100644
--- a/tests/ref/style/show-recursive.png
+++ b/tests/ref/style/show-recursive.png
Binary files differ
diff --git a/tests/ref/text/indent.png b/tests/ref/text/indent.png
index 2d7a2e38..deafc3f6 100644
--- a/tests/ref/text/indent.png
+++ b/tests/ref/text/indent.png
Binary files differ
diff --git a/tests/ref/text/par.png b/tests/ref/text/par.png
index 16622cc2..199c8919 100644
--- a/tests/ref/text/par.png
+++ b/tests/ref/text/par.png
Binary files 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
@@ -16,51 +16,36 @@
= 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() {}