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)