summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/eval/layout.rs4
-rw-r--r--src/eval/mod.rs2
-rw-r--r--src/eval/ops.rs6
-rw-r--r--src/eval/raw.rs49
-rw-r--r--src/frame.rs4
-rw-r--r--src/geom/align.rs2
-rw-r--r--src/library/graphics/transform.rs5
-rw-r--r--src/library/layout/align.rs28
-rw-r--r--src/library/layout/flow.rs5
-rw-r--r--src/library/layout/place.rs2
-rw-r--r--src/library/layout/stack.rs3
-rw-r--r--src/library/mod.rs33
-rw-r--r--src/library/prelude.rs3
-rw-r--r--src/library/text/par.rs21
-rw-r--r--tests/ref/layout/align.pngbin2388 -> 7832 bytes
-rw-r--r--tests/typ/layout/align.typ13
16 files changed, 126 insertions, 54 deletions
diff --git a/src/eval/layout.rs b/src/eval/layout.rs
index 9bf44194..09b69253 100644
--- a/src/eval/layout.rs
+++ b/src/eval/layout.rs
@@ -5,7 +5,7 @@ use std::fmt::{self, Debug, Formatter};
use std::hash::Hash;
use std::sync::Arc;
-use super::{Barrier, StyleChain};
+use super::{Barrier, RawAlign, StyleChain};
use crate::diag::TypResult;
use crate::frame::{Element, Frame, Geometry, Shape, Stroke};
use crate::geom::{
@@ -182,7 +182,7 @@ impl LayoutNode {
}
/// Set alignments for this node.
- pub fn aligned(self, aligns: Spec<Option<Align>>) -> Self {
+ pub fn aligned(self, aligns: Spec<Option<RawAlign>>) -> Self {
if aligns.any(Option::is_some) {
AlignNode { aligns, child: self }.pack()
} else {
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index e83c8159..8b777a64 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -17,6 +17,7 @@ mod func;
mod layout;
mod module;
mod ops;
+mod raw;
mod scope;
mod show;
mod str;
@@ -32,6 +33,7 @@ pub use dict::*;
pub use func::*;
pub use layout::*;
pub use module::*;
+pub use raw::*;
pub use scope::*;
pub use show::*;
pub use styles::*;
diff --git a/src/eval/ops.rs b/src/eval/ops.rs
index ff21d93f..0ba4320e 100644
--- a/src/eval/ops.rs
+++ b/src/eval/ops.rs
@@ -1,8 +1,8 @@
use std::cmp::Ordering;
-use super::{Dynamic, StrExt, Value};
+use super::{Dynamic, RawAlign, StrExt, Value};
use crate::diag::StrResult;
-use crate::geom::{Align, Numeric, Spec, SpecAxis};
+use crate::geom::{Numeric, Spec, SpecAxis};
use Value::*;
/// Bail with a type mismatch error.
@@ -94,7 +94,7 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
if let (Dyn(a), Dyn(b)) = (&a, &b) {
// 1D alignments can be summed into 2D alignments.
if let (Some(&a), Some(&b)) =
- (a.downcast::<Align>(), b.downcast::<Align>())
+ (a.downcast::<RawAlign>(), b.downcast::<RawAlign>())
{
return if a.axis() != b.axis() {
Ok(Dyn(Dynamic::new(match a.axis() {
diff --git a/src/eval/raw.rs b/src/eval/raw.rs
new file mode 100644
index 00000000..337638f9
--- /dev/null
+++ b/src/eval/raw.rs
@@ -0,0 +1,49 @@
+use std::fmt::{self, Debug, Formatter};
+
+use super::{Resolve, StyleChain};
+use crate::geom::{Align, SpecAxis};
+use crate::library::text::ParNode;
+
+/// The unresolved alignment representation.
+#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub enum RawAlign {
+ /// Align at the start side of the text direction.
+ Start,
+ /// Align at the end side of the text direction.
+ End,
+ /// Align at a specific alignment.
+ Specific(Align),
+}
+
+impl Resolve for RawAlign {
+ type Output = Align;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ let dir = styles.get(ParNode::DIR);
+ match self {
+ Self::Start => dir.start().into(),
+ Self::End => dir.end().into(),
+ Self::Specific(align) => align,
+ }
+ }
+}
+
+impl RawAlign {
+ /// The axis this alignment belongs to.
+ pub const fn axis(self) -> SpecAxis {
+ match self {
+ Self::Start | Self::End => SpecAxis::Horizontal,
+ Self::Specific(align) => align.axis(),
+ }
+ }
+}
+
+impl Debug for RawAlign {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ match self {
+ Self::Start => f.pad("left"),
+ Self::End => f.pad("center"),
+ Self::Specific(align) => align.fmt(f),
+ }
+ }
+}
diff --git a/src/frame.rs b/src/frame.rs
index 3c747f0d..5698560f 100644
--- a/src/frame.rs
+++ b/src/frame.rs
@@ -79,8 +79,8 @@ impl Frame {
pub fn resize(&mut self, target: Size, aligns: Spec<Align>) {
if self.size != target {
let offset = Point::new(
- aligns.x.resolve(target.x - self.size.x),
- aligns.y.resolve(target.y - self.size.y),
+ aligns.x.position(target.x - self.size.x),
+ aligns.y.position(target.y - self.size.y),
);
self.size = target;
self.translate(offset);
diff --git a/src/geom/align.rs b/src/geom/align.rs
index be2eac96..3d5f96e5 100644
--- a/src/geom/align.rs
+++ b/src/geom/align.rs
@@ -45,7 +45,7 @@ impl Align {
}
/// Returns the position of this alignment in the given length.
- pub fn resolve(self, length: Length) -> Length {
+ pub fn position(self, length: Length) -> Length {
match self {
Self::Left | Self::Top => Length::zero(),
Self::Center | Self::Horizon => length / 2.0,
diff --git a/src/library/graphics/transform.rs b/src/library/graphics/transform.rs
index eb419a7e..67f9cad9 100644
--- a/src/library/graphics/transform.rs
+++ b/src/library/graphics/transform.rs
@@ -22,7 +22,8 @@ pub type ScaleNode = TransformNode<SCALE>;
#[node]
impl<const T: TransformKind> TransformNode<T> {
/// The origin of the transformation.
- pub const ORIGIN: Spec<Option<Align>> = Spec::default();
+ #[property(resolve)]
+ pub const ORIGIN: Spec<Option<RawAlign>> = Spec::default();
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
let transform = match T {
@@ -61,7 +62,7 @@ impl<const T: TransformKind> Layout for TransformNode<T> {
let mut frames = self.child.layout(ctx, regions, styles)?;
for frame in &mut frames {
- let Spec { x, y } = origin.zip(frame.size).map(|(o, s)| o.resolve(s));
+ let Spec { x, y } = origin.zip(frame.size).map(|(o, s)| o.position(s));
let transform = Transform::translate(x, y)
.pre_concat(self.transform)
.pre_concat(Transform::translate(-x, -y));
diff --git a/src/library/layout/align.rs b/src/library/layout/align.rs
index b08e5fce..699a908c 100644
--- a/src/library/layout/align.rs
+++ b/src/library/layout/align.rs
@@ -5,7 +5,7 @@ use crate::library::text::ParNode;
#[derive(Debug, Hash)]
pub struct AlignNode {
/// How to align the node horizontally and vertically.
- pub aligns: Spec<Option<Align>>,
+ pub aligns: Spec<Option<RawAlign>>,
/// The node to be aligned.
pub child: LayoutNode,
}
@@ -42,30 +42,14 @@ impl Layout for AlignNode {
// Align in the target size. The target size depends on whether we
// should expand.
let target = regions.expand.select(region, frame.size);
- let default = Spec::new(Align::Left, Align::Top);
- let aligns = self.aligns.unwrap_or(default);
+ let aligns = self
+ .aligns
+ .map(|align| align.resolve(styles))
+ .unwrap_or(Spec::new(Align::Left, Align::Top));
+
Arc::make_mut(frame).resize(target, aligns);
}
Ok(frames)
}
}
-
-dynamic! {
- Align: "alignment",
-}
-
-dynamic! {
- Spec<Align>: "2d alignment",
-}
-
-castable! {
- Spec<Option<Align>>,
- Expected: "1d or 2d alignment",
- @align: Align => {
- let mut aligns = Spec::default();
- aligns.set(align.axis(), Some(*align));
- aligns
- },
- @aligns: Spec<Align> => aligns.map(Some),
-}
diff --git a/src/library/layout/flow.rs b/src/library/layout/flow.rs
index ffda6925..a53b0304 100644
--- a/src/library/layout/flow.rs
+++ b/src/library/layout/flow.rs
@@ -189,6 +189,7 @@ impl FlowLayouter {
// Vertical align node alignment is respected by the flow node.
node.downcast::<AlignNode>()
.and_then(|aligned| aligned.aligns.y)
+ .map(|align| align.resolve(styles))
.unwrap_or(Align::Top),
);
@@ -238,8 +239,8 @@ impl FlowLayouter {
}
FlowItem::Frame(frame, aligns) => {
ruler = ruler.max(aligns.y);
- let x = aligns.x.resolve(size.x - frame.size.x);
- let y = offset + ruler.resolve(size.y - self.used.y);
+ let x = aligns.x.position(size.x - frame.size.x);
+ let y = offset + ruler.position(size.y - self.used.y);
let pos = Point::new(x, y);
offset += frame.size.y;
output.push_frame(pos, frame);
diff --git a/src/library/layout/place.rs b/src/library/layout/place.rs
index 2d4ebc4d..eefa6a9b 100644
--- a/src/library/layout/place.rs
+++ b/src/library/layout/place.rs
@@ -8,9 +8,9 @@ pub struct PlaceNode(pub LayoutNode);
#[node]
impl PlaceNode {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
- let aligns = args.find()?.unwrap_or(Spec::with_x(Some(Align::Left)));
let tx = args.named("dx")?.unwrap_or_default();
let ty = args.named("dy")?.unwrap_or_default();
+ let aligns = args.find()?.unwrap_or(Spec::with_x(Some(RawAlign::Start)));
let body: LayoutNode = args.expect("body")?;
Ok(Content::block(Self(
body.moved(Point::new(tx, ty)).aligned(aligns),
diff --git a/src/library/layout/stack.rs b/src/library/layout/stack.rs
index b0e2e160..312757f3 100644
--- a/src/library/layout/stack.rs
+++ b/src/library/layout/stack.rs
@@ -175,6 +175,7 @@ impl StackLayouter {
let align = node
.downcast::<AlignNode>()
.and_then(|node| node.aligns.get(self.axis))
+ .map(|align| align.resolve(styles))
.unwrap_or(self.dir.start().into());
let frames = node.layout(ctx, &self.regions, styles)?;
@@ -229,7 +230,7 @@ impl StackLayouter {
// Align along the block axis.
let parent = size.get(self.axis);
let child = frame.size.get(self.axis);
- let block = ruler.resolve(parent - self.used.main)
+ let block = ruler.position(parent - self.used.main)
+ if self.dir.is_positive() {
cursor
} else {
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 7c5a519f..358c2204 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -113,12 +113,14 @@ pub fn new() -> Scope {
std.def_const("rtl", Dir::RTL);
std.def_const("ttb", Dir::TTB);
std.def_const("btt", Dir::BTT);
- std.def_const("left", Align::Left);
- std.def_const("center", Align::Center);
- std.def_const("right", Align::Right);
- std.def_const("top", Align::Top);
- std.def_const("horizon", Align::Horizon);
- std.def_const("bottom", Align::Bottom);
+ std.def_const("start", RawAlign::Start);
+ std.def_const("end", RawAlign::End);
+ std.def_const("left", RawAlign::Specific(Align::Left));
+ std.def_const("center", RawAlign::Specific(Align::Center));
+ std.def_const("right", RawAlign::Specific(Align::Right));
+ std.def_const("top", RawAlign::Specific(Align::Top));
+ std.def_const("horizon", RawAlign::Specific(Align::Horizon));
+ std.def_const("bottom", RawAlign::Specific(Align::Bottom));
std
}
@@ -127,6 +129,25 @@ dynamic! {
Dir: "direction",
}
+dynamic! {
+ RawAlign: "alignment",
+}
+
+dynamic! {
+ Spec<RawAlign>: "2d alignment",
+}
+
+castable! {
+ Spec<Option<RawAlign>>,
+ Expected: "1d or 2d alignment",
+ @align: RawAlign => {
+ let mut aligns = Spec::default();
+ aligns.set(align.axis(), Some(*align));
+ aligns
+ },
+ @aligns: Spec<RawAlign> => aligns.map(Some),
+}
+
castable! {
usize,
Expected: "non-negative integer",
diff --git a/src/library/prelude.rs b/src/library/prelude.rs
index a2e296fa..f052a43a 100644
--- a/src/library/prelude.rs
+++ b/src/library/prelude.rs
@@ -10,7 +10,8 @@ pub use typst_macros::node;
pub use crate::diag::{with_alternative, At, Error, StrResult, TypError, TypResult};
pub use crate::eval::{
Arg, Args, Array, Cast, Content, Dict, Fold, Func, Key, Layout, LayoutNode, Merge,
- Node, Regions, Scope, Show, ShowNode, Smart, StyleChain, StyleMap, StyleVec, Value,
+ Node, RawAlign, Regions, Resolve, Scope, Show, ShowNode, Smart, StyleChain, StyleMap,
+ StyleVec, Value,
};
pub use crate::frame::*;
pub use crate::geom::*;
diff --git a/src/library/text/par.rs b/src/library/text/par.rs
index 2d31cd11..dc7c9dcf 100644
--- a/src/library/text/par.rs
+++ b/src/library/text/par.rs
@@ -33,7 +33,8 @@ impl ParNode {
/// The direction for text and inline objects.
pub const DIR: Dir = Dir::LTR;
/// How to align text and inline objects in their line.
- pub const ALIGN: Align = Align::Left;
+ #[property(resolve)]
+ pub const ALIGN: RawAlign = RawAlign::Start;
/// Whether to justify text in its line.
pub const JUSTIFY: bool = false;
/// How to determine line breaks.
@@ -74,15 +75,13 @@ impl ParNode {
dir = Some(v);
}
- let align =
- if let Some(Spanned { v, span }) = args.named::<Spanned<Align>>("align")? {
- if v.axis() != SpecAxis::Horizontal {
- bail!(span, "must be horizontal");
- }
- Some(v)
- } else {
- dir.map(|dir| dir.start().into())
- };
+ let mut align = None;
+ if let Some(Spanned { v, span }) = args.named::<Spanned<RawAlign>>("align")? {
+ if v.axis() != SpecAxis::Horizontal {
+ bail!(span, "must be horizontal");
+ }
+ align = Some(v);
+ };
styles.set_opt(Self::LANG, lang);
styles.set_opt(Self::DIR, dir);
@@ -880,7 +879,7 @@ fn commit(
// Construct the line's frame from left to right.
for item in reordered {
let mut position = |frame: Frame| {
- let x = offset + align.resolve(remaining);
+ let x = offset + align.position(remaining);
let y = line.baseline - frame.baseline();
offset += frame.size.x;
output.merge_frame(Point::new(x, y), frame);
diff --git a/tests/ref/layout/align.png b/tests/ref/layout/align.png
index 77619b32..5dde0cef 100644
--- a/tests/ref/layout/align.png
+++ b/tests/ref/layout/align.png
Binary files differ
diff --git a/tests/typ/layout/align.typ b/tests/typ/layout/align.typ
index 09c4dee5..f0603b46 100644
--- a/tests/typ/layout/align.typ
+++ b/tests/typ/layout/align.typ
@@ -14,6 +14,7 @@
))
---
+// Test that multiple paragraphs in subflow also respect alignment.
#align(center)[
Lorem Ipsum
@@ -21,6 +22,18 @@
]
---
+// Test start and end alignment.
+#rotate(-30deg, origin: end + horizon)[Hello]
+
+#set par(lang: "de")
+#align(start)[Start]
+#align(end)[Ende]
+
+#set par(lang: "ar")
+#align(start)[يبدأ]
+#align(end)[نهاية]
+
+---
// Ref: false
#test(type(center), "alignment")
#test(type(horizon), "alignment")