diff --git a/library/src/layout/align.rs b/library/src/layout/align.rs index ae60b4c6a..506ef6846 100644 --- a/library/src/layout/align.rs +++ b/library/src/layout/align.rs @@ -1,62 +1,22 @@ -use super::{HorizontalAlign, ParNode}; use crate::prelude::*; -/// Align content along the layouting axes. +/// Just an empty shell to scope styles. #[derive(Debug, Hash)] -pub struct AlignNode { - /// How to align the content horizontally and vertically. - pub aligns: Axes>, - /// The content to be aligned. - pub body: Content, -} +pub enum AlignNode {} -#[node(Layout)] +#[node] impl AlignNode { + /// The alignment. + #[property(fold, skip)] + pub const ALIGNS: Axes> = + Axes::new(GenAlign::Start, GenAlign::Specific(Align::Top)); + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + args.expect("body") + } + + fn set(...) { let aligns: Axes> = args.find()?.unwrap_or_default(); - let body: Content = args.expect("body")?; - - if let Axes { x: Some(x), y: None } = aligns { - if !body.has::() || body.has::() { - return Ok(body.styled(ParNode::ALIGN, HorizontalAlign(x))); - } - } - - Ok(Self { aligns, body }.pack()) - } -} - -impl Layout for AlignNode { - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult { - // The child only needs to expand along an axis if there's no alignment. - let mut pod = regions.clone(); - pod.expand &= self.aligns.as_ref().map(Option::is_none); - - // Align paragraphs inside the child. - let mut map = StyleMap::new(); - if let Some(align) = self.aligns.x { - map.set(ParNode::ALIGN, HorizontalAlign(align)); - } - - // Layout the child. - let mut fragment = self.body.layout(vt, styles.chain(&map), pod)?; - for (region, frame) in regions.iter().zip(&mut fragment) { - // Align in the target size. The target size depends on whether we - // should expand. - let target = regions.expand.select(region, frame.size()); - let aligns = self - .aligns - .map(|align| align.resolve(styles)) - .unwrap_or(Axes::new(Align::Left, Align::Top)); - - frame.resize(target, aligns); - } - - Ok(fragment) + styles.set(Self::ALIGNS, aligns); } } diff --git a/library/src/layout/flow.rs b/library/src/layout/flow.rs index 0b6454a07..b78f3932d 100644 --- a/library/src/layout/flow.rs +++ b/library/src/layout/flow.rs @@ -125,7 +125,7 @@ impl<'a> FlowLayouter<'a> { par: &ParNode, styles: StyleChain, ) -> SourceResult<()> { - let aligns = Axes::new(styles.get(ParNode::ALIGN), Align::Top); + let aligns = styles.get(AlignNode::ALIGNS).resolve(styles); let leading = styles.get(ParNode::LEADING); let consecutive = self.last_was_par; let fragment = par.layout( @@ -172,17 +172,7 @@ impl<'a> FlowLayouter<'a> { } // How to align the block. - let aligns = Axes::new( - // For non-expanding paragraphs it is crucial that we align the - // whole paragraph as it is itself aligned. - styles.get(ParNode::ALIGN), - // Vertical align node alignment is respected by the flow. - block - .to::() - .and_then(|aligned| aligned.aligns.y) - .map(|align| align.resolve(styles)) - .unwrap_or(Align::Top), - ); + let aligns = styles.get(AlignNode::ALIGNS).resolve(styles); // Layout the block itself. let sticky = styles.get(BlockNode::STICKY); diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs index 00b1f9bec..afa1344fe 100644 --- a/library/src/layout/mod.rs +++ b/library/src/layout/mod.rs @@ -412,7 +412,9 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> { bail!(span, "not allowed here"); } self.interrupt_page(styles)?; - } else if map.interruption::().is_some() { + } else if map.interruption::().is_some() + || map.interruption::().is_some() + { self.interrupt_par()?; } else if map.interruption::().is_some() || map.interruption::().is_some() diff --git a/library/src/layout/par.rs b/library/src/layout/par.rs index c0e7c6c90..d93bfba73 100644 --- a/library/src/layout/par.rs +++ b/library/src/layout/par.rs @@ -5,6 +5,7 @@ use xi_unicode::LineBreakIterator; use typst::model::Key; use super::{HNode, RepeatNode, Spacing}; +use crate::layout::AlignNode; use crate::prelude::*; use crate::text::{ shape, LinebreakNode, Quoter, Quotes, ShapedText, SmartQuoteNode, SpaceNode, TextNode, @@ -22,9 +23,6 @@ impl ParNode { /// 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); /// Whether to justify text in its line. pub const JUSTIFY: bool = false; /// How to determine line breaks. @@ -554,7 +552,7 @@ fn prepare<'a>( styles, hyphenate: shared_get(styles, &par.0, TextNode::HYPHENATE), lang: shared_get(styles, &par.0, TextNode::LANG), - align: styles.get(ParNode::ALIGN), + align: styles.get(AlignNode::ALIGNS).x.resolve(styles), justify: styles.get(ParNode::JUSTIFY), }) } diff --git a/library/src/layout/place.rs b/library/src/layout/place.rs index 2e2c81a1d..28d231b73 100644 --- a/library/src/layout/place.rs +++ b/library/src/layout/place.rs @@ -1,9 +1,8 @@ -use super::AlignNode; use crate::prelude::*; /// Place content at an absolute position. #[derive(Debug, Hash)] -pub struct PlaceNode(pub Content); +pub struct PlaceNode(pub Content, bool); #[node(Layout, Behave)] impl PlaceNode { @@ -12,7 +11,8 @@ impl PlaceNode { let dx = args.named("dx")?.unwrap_or_default(); let dy = args.named("dy")?.unwrap_or_default(); let body = args.expect::("body")?; - Ok(Self(body.moved(Axes::new(dx, dy)).aligned(aligns)).pack()) + let out_of_flow = aligns.y.is_some(); + Ok(Self(body.moved(Axes::new(dx, dy)).aligned(aligns), out_of_flow).pack()) } } @@ -49,7 +49,7 @@ impl PlaceNode { /// origin. Instead of relative to the parent's current flow/cursor /// position. pub fn out_of_flow(&self) -> bool { - self.0.to::().map_or(false, |node| node.aligns.y.is_some()) + self.1 } } diff --git a/library/src/layout/stack.rs b/library/src/layout/stack.rs index 6432f4acc..d44dcb482 100644 --- a/library/src/layout/stack.rs +++ b/library/src/layout/stack.rs @@ -1,6 +1,6 @@ use typst::model::StyledNode; -use super::{AlignNode, ParNode, Spacing}; +use super::{AlignNode, Spacing}; use crate::prelude::*; /// Arrange content and spacing along an axis. @@ -180,21 +180,13 @@ impl<'a> StackLayouter<'a> { // Block-axis alignment of the `AlignNode` is respected // by the stack node. - let align = block - .to::() - .and_then(|node| node.aligns.get(self.axis)) - .map(|align| align.resolve(styles)) - .unwrap_or_else(|| { - if let Some(styled) = block.to::() { - let map = &styled.map; - if map.contains(ParNode::ALIGN) { - return StyleChain::new(map).get(ParNode::ALIGN); - } - } - - self.dir.start().into() - }); + let aligns = if let Some(styled) = block.to::() { + styles.chain(&styled.map).get(AlignNode::ALIGNS) + } else { + styles.get(AlignNode::ALIGNS) + }; + let align = aligns.get(self.axis).resolve(styles); let fragment = block.layout(vt, styles, self.regions)?; let len = fragment.len(); for (i, frame) in fragment.into_iter().enumerate() { diff --git a/library/src/lib.rs b/library/src/lib.rs index e41e7c0d9..86bef0b94 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -208,6 +208,6 @@ fn items() -> LangItems { math_atom: |atom| math::AtomNode(atom).pack(), math_script: |base, sub, sup| math::ScriptNode { base, sub, sup }.pack(), math_frac: |num, denom| math::FracNode { num, denom }.pack(), - math_align: |count| math::AlignNode(count).pack(), + math_align_point: |count| math::AlignPointNode(count).pack(), } } diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index 1139296cf..62432b128 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -442,14 +442,14 @@ impl Texify for ScriptNode { } } -/// A math alignment indicator: `&`, `&&`. +/// A math alignment point: `&`, `&&`. #[derive(Debug, Hash)] -pub struct AlignNode(pub usize); +pub struct AlignPointNode(pub usize); #[node(Texify)] -impl AlignNode {} +impl AlignPointNode {} -impl Texify for AlignNode { +impl Texify for AlignPointNode { fn texify(&self, _: &mut Texifier) -> SourceResult<()> { Ok(()) } diff --git a/library/src/shared/ext.rs b/library/src/shared/ext.rs index 811ae7573..23db71f91 100644 --- a/library/src/shared/ext.rs +++ b/library/src/shared/ext.rs @@ -57,7 +57,7 @@ impl ContentExt for Content { } fn aligned(self, aligns: Axes>) -> Self { - crate::layout::AlignNode { aligns, body: self }.pack() + self.styled(crate::layout::AlignNode::ALIGNS, aligns) } fn padded(self, padding: Sides>) -> Self { diff --git a/src/geom/corners.rs b/src/geom/corners.rs index d84160ccd..386acbfb2 100644 --- a/src/geom/corners.rs +++ b/src/geom/corners.rs @@ -45,16 +45,13 @@ impl Corners { } } - /// Zip two instances into an instance. - pub fn zip(self, other: Corners, mut f: F) -> Corners - where - F: FnMut(T, V) -> W, - { + /// Zip two instances into one. + pub fn zip(self, other: Corners) -> Corners<(T, U)> { Corners { - top_left: f(self.top_left, other.top_left), - top_right: f(self.top_right, other.top_right), - bottom_right: f(self.bottom_right, other.bottom_right), - bottom_left: f(self.bottom_left, other.bottom_left), + top_left: (self.top_left, other.top_left), + top_right: (self.top_right, other.top_right), + bottom_right: (self.bottom_right, other.bottom_right), + bottom_left: (self.bottom_left, other.bottom_left), } } diff --git a/src/geom/sides.rs b/src/geom/sides.rs index 9b8d9a6b7..40327a425 100644 --- a/src/geom/sides.rs +++ b/src/geom/sides.rs @@ -45,16 +45,13 @@ impl Sides { } } - /// Zip two instances into an instance. - pub fn zip(self, other: Sides, mut f: F) -> Sides - where - F: FnMut(T, V) -> W, - { + /// Zip two instances into one. + pub fn zip(self, other: Sides) -> Sides<(T, U)> { Sides { - left: f(self.left, other.left), - top: f(self.top, other.top), - right: f(self.right, other.right), - bottom: f(self.bottom, other.bottom), + left: (self.left, other.left), + top: (self.top, other.top), + right: (self.right, other.right), + bottom: (self.bottom, other.bottom), } } diff --git a/src/model/eval.rs b/src/model/eval.rs index 0ae8a0b1d..53a393c33 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -429,7 +429,7 @@ impl Eval for ast::MathNode { Self::Symbol(v) => (vm.items.symbol)(v.get().clone() + ":op".into()), Self::Script(v) => v.eval(vm)?, Self::Frac(v) => v.eval(vm)?, - Self::Align(v) => v.eval(vm)?, + Self::AlignPoint(v) => v.eval(vm)?, Self::Group(v) => v.eval(vm)?, Self::Expr(v) => { if let ast::Expr::Ident(ident) = v { @@ -480,11 +480,11 @@ impl Eval for ast::Frac { } } -impl Eval for ast::Align { +impl Eval for ast::AlignPoint { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult { - Ok((vm.items.math_align)(self.count())) + Ok((vm.items.math_align_point)(self.count())) } } diff --git a/src/model/library.rs b/src/model/library.rs index 63bd58399..02eb9179c 100644 --- a/src/model/library.rs +++ b/src/model/library.rs @@ -74,8 +74,8 @@ pub struct LangItems { fn(base: Content, sub: Option, sup: Option) -> Content, /// A fraction in a formula: `x/2`. pub math_frac: fn(num: Content, denom: Content) -> Content, - /// An alignment indicator in a formula: `&`, `&&`. - pub math_align: fn(count: usize) -> Content, + /// An alignment point in a formula: `&`, `&&`. + pub math_align_point: fn(count: usize) -> Content, } impl Debug for LangItems { @@ -107,7 +107,7 @@ impl Hash for LangItems { self.math_atom.hash(state); self.math_script.hash(state); self.math_frac.hash(state); - self.math_align.hash(state); + self.math_align_point.hash(state); } } diff --git a/src/model/styles.rs b/src/model/styles.rs index 37596b8d0..b2c328fa7 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -913,6 +913,14 @@ where } } +impl Fold for Axes> { + type Output = Axes; + + fn fold(self, outer: Self::Output) -> Self::Output { + self.zip(outer).map(|(inner, outer)| inner.unwrap_or(outer)) + } +} + impl Fold for Sides where T: Fold, @@ -920,7 +928,7 @@ where type Output = Sides; fn fold(self, outer: Self::Output) -> Self::Output { - self.zip(outer, |inner, outer| inner.fold(outer)) + self.zip(outer).map(|(inner, outer)| inner.fold(outer)) } } @@ -928,7 +936,7 @@ impl Fold for Sides>> { type Output = Sides>; fn fold(self, outer: Self::Output) -> Self::Output { - self.zip(outer, |inner, outer| inner.unwrap_or(outer)) + self.zip(outer).map(|(inner, outer)| inner.unwrap_or(outer)) } } @@ -936,7 +944,7 @@ impl Fold for Sides>>> { type Output = Sides>>; fn fold(self, outer: Self::Output) -> Self::Output { - self.zip(outer, |inner, outer| inner.unwrap_or(outer)) + self.zip(outer).map(|(inner, outer)| inner.unwrap_or(outer)) } } @@ -947,7 +955,7 @@ where type Output = Corners; fn fold(self, outer: Self::Output) -> Self::Output { - self.zip(outer, |inner, outer| inner.fold(outer)) + self.zip(outer).map(|(inner, outer)| inner.fold(outer)) } } @@ -955,7 +963,7 @@ impl Fold for Corners>> { type Output = Corners>; fn fold(self, outer: Self::Output) -> Self::Output { - self.zip(outer, |inner, outer| inner.unwrap_or(outer)) + self.zip(outer).map(|(inner, outer)| inner.unwrap_or(outer)) } } diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 3661c156f..4db005930 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -453,8 +453,8 @@ pub enum MathNode { Script(Script), /// A fraction: `x/2`. Frac(Frac), - /// An alignment indicator: `&`, `&&`. - Align(Align), + /// An alignment point: `&`, `&&`. + AlignPoint(AlignPoint), /// Grouped mathematical material. Group(Math), /// An expression. @@ -472,7 +472,7 @@ impl AstNode for MathNode { SyntaxKind::Symbol(_) => node.cast().map(Self::Symbol), SyntaxKind::Script => node.cast().map(Self::Script), SyntaxKind::Frac => node.cast().map(Self::Frac), - SyntaxKind::Align => node.cast().map(Self::Align), + SyntaxKind::AlignPoint => node.cast().map(Self::AlignPoint), SyntaxKind::Math => node.cast().map(Self::Group), _ => node.cast().map(Self::Expr), } @@ -488,7 +488,7 @@ impl AstNode for MathNode { Self::Symbol(v) => v.as_untyped(), Self::Script(v) => v.as_untyped(), Self::Frac(v) => v.as_untyped(), - Self::Align(v) => v.as_untyped(), + Self::AlignPoint(v) => v.as_untyped(), Self::Group(v) => v.as_untyped(), Self::Expr(v) => v.as_untyped(), } @@ -558,11 +558,11 @@ impl Frac { } node! { - /// An alignment indicator in a formula: `&`, `&&`. - Align + /// An alignment point in a formula: `&`, `&&`. + AlignPoint } -impl Align { +impl AlignPoint { /// The number of ampersands. pub fn count(&self) -> usize { self.0.children().filter(|n| n.kind() == &SyntaxKind::Amp).count() diff --git a/src/syntax/highlight.rs b/src/syntax/highlight.rs index 3fed905fb..f9f359442 100644 --- a/src/syntax/highlight.rs +++ b/src/syntax/highlight.rs @@ -302,7 +302,7 @@ impl Category { SyntaxKind::Atom(_) => None, SyntaxKind::Script => None, SyntaxKind::Frac => None, - SyntaxKind::Align => None, + SyntaxKind::AlignPoint => None, SyntaxKind::Ident(_) => match parent.kind() { SyntaxKind::Markup { .. } diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs index a4eb317b6..d5fda7f8a 100644 --- a/src/syntax/kind.rs +++ b/src/syntax/kind.rs @@ -179,8 +179,8 @@ pub enum SyntaxKind { Script, /// A fraction in a formula: `x/2`. Frac, - /// An alignment indicator in a formula: `&`, `&&`. - Align, + /// An alignment point in a formula: `&`, `&&`. + AlignPoint, /// An identifier: `it`. Ident(EcoString), @@ -408,7 +408,7 @@ impl SyntaxKind { Self::Atom(_) => "math atom", Self::Script => "script", Self::Frac => "fraction", - Self::Align => "alignment indicator", + Self::AlignPoint => "alignment point", Self::Ident(_) => "identifier", Self::Bool(_) => "boolean", Self::Int(_) => "integer", @@ -528,7 +528,7 @@ impl Hash for SyntaxKind { Self::Atom(c) => c.hash(state), Self::Script => {} Self::Frac => {} - Self::Align => {} + Self::AlignPoint => {} Self::Ident(v) => v.hash(state), Self::Bool(v) => v.hash(state), Self::Int(v) => v.hash(state), diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index 97570950a..d751b6aab 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -499,7 +499,7 @@ fn math_group(p: &mut Parser, group: Group) { } fn math_align(p: &mut Parser) { - p.perform(SyntaxKind::Align, |p| { + p.perform(SyntaxKind::AlignPoint, |p| { p.assert(SyntaxKind::Amp); while p.eat_if(SyntaxKind::Amp) {} }) diff --git a/tests/ref/compiler/construct.png b/tests/ref/compiler/construct.png index 43cf5d796..829b072b5 100644 Binary files a/tests/ref/compiler/construct.png and b/tests/ref/compiler/construct.png differ diff --git a/tests/ref/layout/stack-1.png b/tests/ref/layout/stack-1.png index 167fd84cf..54ee1a08b 100644 Binary files a/tests/ref/layout/stack-1.png and b/tests/ref/layout/stack-1.png differ diff --git a/tests/typ/compiler/construct.typ b/tests/typ/compiler/construct.typ index f535184c2..df5368b7d 100644 --- a/tests/typ/compiler/construct.typ +++ b/tests/typ/compiler/construct.typ @@ -10,9 +10,9 @@ // Ensure that constructor styles win, but not over outer styles. // The outer paragraph should be right-aligned, // but the B should be center-aligned. -#set par(align: center) -#par(align: right)[ - A #rect(width: 2cm, fill: conifer, inset: 4pt)[B] +#set list(label: [>]) +#list(label: [--])[ + #rect(width: 2cm, fill: conifer, inset: 4pt, list[A]) ] --- diff --git a/tests/typ/layout/par.typ b/tests/typ/layout/par.typ index 558059e94..aabe63ef8 100644 --- a/tests/typ/layout/par.typ +++ b/tests/typ/layout/par.typ @@ -2,7 +2,7 @@ --- // Test ragged-left. -#set par(align: right) +#set align(right) To the right! Where the sunlight peeks behind the mountain. --- @@ -35,11 +35,3 @@ fn main() {} - List Paragraph - ---- -// Error: 17-20 must be horizontal -#set par(align: top) - ---- -// Error: 17-33 expected alignment, found 2d alignment -#set par(align: horizon + center) diff --git a/tests/typ/layout/repeat.typ b/tests/typ/layout/repeat.typ index 13e99b519..3b5459c94 100644 --- a/tests/typ/layout/repeat.typ +++ b/tests/typ/layout/repeat.typ @@ -32,7 +32,7 @@ A#repeat(rect(width: 2.5em, height: 1em))B // Test single repeat in both directions. A#repeat(rect(width: 6em, height: 0.7em))B -#set par(align: center) +#set align(center) A#repeat(rect(width: 6em, height: 0.7em))B #set text(dir: rtl) diff --git a/tests/typ/layout/spacing.typ b/tests/typ/layout/spacing.typ index eb0bd39e5..3cb1a20ca 100644 --- a/tests/typ/layout/spacing.typ +++ b/tests/typ/layout/spacing.typ @@ -19,7 +19,7 @@ Add #h(10pt) #h(10pt) up --- // Test spacing collapsing before spacing. -#set par(align: right) +#set align(right) A #h(0pt) B #h(0pt) \ A B \ A #h(-1fr) B diff --git a/tests/typ/math/style.typ b/tests/typ/math/style.typ index c9238a9ae..8df216ef9 100644 --- a/tests/typ/math/style.typ +++ b/tests/typ/math/style.typ @@ -11,5 +11,5 @@ } #set page(width: auto) -#set par(align: center) +#set align(center) #table(columns: 1 + modifiers.len(), ..cells) diff --git a/tests/typ/text/microtype.typ b/tests/typ/text/microtype.typ index 57f9b2f23..c1f0d344e 100644 --- a/tests/typ/text/microtype.typ +++ b/tests/typ/text/microtype.typ @@ -18,6 +18,6 @@ --- // Test that lone punctuation doesn't overhang into the margin. #set page(margin: 0pt) -#set par(align: end) +#set align(end) #set text(dir: rtl) : diff --git a/tests/typ/visualize/line.typ b/tests/typ/visualize/line.typ index 2cb2fc9c8..92490ef80 100644 --- a/tests/typ/visualize/line.typ +++ b/tests/typ/visualize/line.typ @@ -22,7 +22,7 @@ #let star(width, ..args) = box(width: width, height: width)[ #set text(spacing: 0%) #set line(..args) - #set par(align: left) + #set align(left) #line(length: +30%, origin: (09.0%, 02%)) #line(length: +30%, origin: (38.7%, 02%), angle: -72deg) #line(length: +30%, origin: (57.5%, 02%), angle: 252deg)