diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-11-23 12:45:20 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-11-23 12:45:20 +0100 |
| commit | 4f9e5819bbab1f93ad4f4b789038c60487a76368 (patch) | |
| tree | 488a4e0422db4531d9882cf08f0b5cc21ae55a23 | |
| parent | d3f6040cedacad1b6c323be721c9086f6c5d9a44 (diff) | |
2d alignments with plus operator
| -rw-r--r-- | src/eval/mod.rs | 2 | ||||
| -rw-r--r-- | src/eval/ops.rs | 23 | ||||
| -rw-r--r-- | src/eval/value.rs | 6 | ||||
| -rw-r--r-- | src/geom/spec.rs | 11 | ||||
| -rw-r--r-- | src/layout/mod.rs | 18 | ||||
| -rw-r--r-- | src/library/align.rs | 20 | ||||
| -rw-r--r-- | src/library/image.rs | 5 | ||||
| -rw-r--r-- | src/library/mod.rs | 9 | ||||
| -rw-r--r-- | src/library/par.rs | 10 | ||||
| -rw-r--r-- | src/library/placed.rs | 11 | ||||
| -rw-r--r-- | src/library/shape.rs | 24 | ||||
| -rw-r--r-- | src/library/sized.rs | 10 | ||||
| -rw-r--r-- | src/library/transform.rs | 5 | ||||
| -rw-r--r-- | tests/typ/elements/circle.typ | 6 | ||||
| -rw-r--r-- | tests/typ/elements/ellipse.typ | 2 | ||||
| -rw-r--r-- | tests/typ/elements/image.typ | 2 | ||||
| -rw-r--r-- | tests/typ/layout/align.typ | 16 | ||||
| -rw-r--r-- | tests/typ/layout/background.typ | 2 | ||||
| -rw-r--r-- | tests/typ/layout/move.typ | 2 | ||||
| -rw-r--r-- | tests/typ/layout/page.typ | 4 | ||||
| -rw-r--r-- | tests/typ/layout/placed.typ | 2 | ||||
| -rw-r--r-- | tests/typ/text/par.typ | 4 |
22 files changed, 113 insertions, 81 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 1ff497e8..6b28e5ab 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -422,7 +422,7 @@ impl Eval for CallArgs { } v => { if let Value::Dyn(dynamic) = &v { - if let Some(args) = dynamic.downcast_ref::<Args>() { + if let Some(args) = dynamic.downcast::<Args>() { items.extend(args.items.iter().cloned()); continue; } diff --git a/src/eval/ops.rs b/src/eval/ops.rs index e40fa78d..6fac354d 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -1,8 +1,9 @@ use std::cmp::Ordering; use std::convert::TryFrom; -use super::Value; +use super::{Dynamic, Value}; use crate::diag::StrResult; +use crate::geom::{Align, Get, Spec}; use crate::util::EcoString; use Value::*; @@ -87,7 +88,25 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> { (Template(a), Str(b)) => Template(a + b), (Str(a), Template(b)) => Template(a + b), - (a, b) => mismatch!("cannot add {} and {}", a, b), + (a, b) => { + 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>()) + { + if a.axis() == b.axis() { + return Err(format!("cannot add two {:?} alignments", a.axis())); + } + + let mut aligns = Spec::default(); + aligns.set(a.axis(), Some(a)); + aligns.set(b.axis(), Some(b)); + return Ok(Dyn(Dynamic::new(aligns))); + } + } + + mismatch!("cannot add {} and {}", a, b); + } }) } diff --git a/src/eval/value.rs b/src/eval/value.rs index dec5c6c0..16e8b810 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -188,7 +188,7 @@ impl Dynamic { } /// Try to downcast to a reference to a specific type. - pub fn downcast_ref<T: 'static>(&self) -> Option<&T> { + pub fn downcast<T: 'static>(&self) -> Option<&T> { self.0.as_any().downcast_ref() } @@ -225,7 +225,7 @@ where } fn dyn_eq(&self, other: &Dynamic) -> bool { - if let Some(other) = other.downcast_ref::<Self>() { + if let Some(other) = other.downcast::<Self>() { self == other } else { false @@ -334,7 +334,7 @@ macro_rules! castable { let found = match value { $($pattern => return Ok($out),)* $crate::eval::Value::Dyn(dynamic) => { - $(if let Some($dyn_in) = dynamic.downcast_ref::<$dyn_type>() { + $(if let Some($dyn_in) = dynamic.downcast::<$dyn_type>() { return Ok($dyn_out); })* dynamic.type_name() diff --git a/src/geom/spec.rs b/src/geom/spec.rs index 576c1c89..02263481 100644 --- a/src/geom/spec.rs +++ b/src/geom/spec.rs @@ -123,7 +123,7 @@ impl<T: Debug> Debug for Spec<T> { } /// The two specific layouting axes. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Copy, Clone, Eq, PartialEq, Hash)] pub enum SpecAxis { /// The horizontal layouting axis. Horizontal, @@ -150,3 +150,12 @@ impl SpecAxis { } } } + +impl Debug for SpecAxis { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + Self::Horizontal => "horizontal", + Self::Vertical => "vertical", + }) + } +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 8f46c049..be4e994c 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -104,27 +104,27 @@ impl PackedNode { } /// Force a size for this node. - pub fn sized(self, w: Option<Linear>, h: Option<Linear>) -> Self { - if w.is_some() || h.is_some() { - SizedNode { child: self, sizing: Spec::new(w, h) }.pack() + pub fn sized(self, sizing: Spec<Option<Linear>>) -> Self { + if sizing.any(Option::is_some) { + SizedNode { child: self, sizing }.pack() } else { self } } /// Set alignments for this node. - pub fn aligned(self, x: Option<Align>, y: Option<Align>) -> Self { - if x.is_some() || y.is_some() { - AlignNode { child: self, aligns: Spec::new(x, y) }.pack() + pub fn aligned(self, aligns: Spec<Option<Align>>) -> Self { + if aligns.any(Option::is_some) { + AlignNode { child: self, aligns }.pack() } else { self } } /// Move this node's contents without affecting layout. - pub fn moved(self, dx: Option<Linear>, dy: Option<Linear>) -> Self { - if dx.is_some() || dy.is_some() { - MoveNode { child: self, offset: Spec::new(dx, dy) }.pack() + pub fn moved(self, offset: Spec<Option<Linear>>) -> Self { + if offset.any(Option::is_some) { + MoveNode { child: self, offset }.pack() } else { self } diff --git a/src/library/align.rs b/src/library/align.rs index 97196aa7..7ce749d1 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -2,32 +2,18 @@ use super::prelude::*; /// `align`: Configure the alignment along the layouting axes. pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - let Spec { x, y } = parse_aligns(args)?; + let aligns = args.expect::<Spec<_>>("alignment")?; let body = args.expect::<Template>("body")?; Ok(Value::Template(Template::from_block(move |style| { let mut style = style.clone(); - if let Some(x) = x { + if let Some(x) = aligns.x { style.par_mut().align = x; } - body.pack(&style).aligned(x, y) + body.pack(&style).aligned(aligns) }))) } -/// Parse alignment arguments with shorthand. -pub(super) fn parse_aligns(args: &mut Args) -> TypResult<Spec<Option<Align>>> { - let mut x = args.named("horizontal")?; - let mut y = args.named("vertical")?; - for Spanned { v, span } in args.all::<Spanned<Align>>() { - match v.axis() { - SpecAxis::Horizontal if x.is_none() => x = Some(v), - SpecAxis::Vertical if y.is_none() => y = Some(v), - _ => bail!(span, "unexpected argument"), - } - } - Ok(Spec::new(x, y)) -} - /// A node that aligns its child. #[derive(Debug, Hash)] pub struct AlignNode { diff --git a/src/library/image.rs b/src/library/image.rs index b0d66a63..a53eacc5 100644 --- a/src/library/image.rs +++ b/src/library/image.rs @@ -7,8 +7,7 @@ use crate::image::ImageId; /// `image`: An image. pub fn image(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { let path = args.expect::<Spanned<EcoString>>("path to image file")?; - let width = args.named("width")?; - let height = args.named("height")?; + let sizing = Spec::new(args.named("width")?, args.named("height")?); let fit = args.named("fit")?.unwrap_or_default(); // Load the image. @@ -21,7 +20,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { })?; Ok(Value::Template(Template::from_inline(move |_| { - ImageNode { id, fit }.pack().sized(width, height) + ImageNode { id, fit }.pack().sized(sizing) }))) } diff --git a/src/library/mod.rs b/src/library/mod.rs index 9fe15a0f..9e7f6f28 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -142,6 +142,15 @@ dynamic! { } dynamic! { + Spec<Option<Align>>: "2d alignment", + @align: Align => { + let mut aligns = Spec::default(); + aligns.set(align.axis(), Some(*align)); + aligns + }, +} + +dynamic! { FontFamily: "font family", Value::Str(string) => Self::Named(string.to_lowercase()), } diff --git a/src/library/par.rs b/src/library/par.rs index 4d291fdc..5edffeaa 100644 --- a/src/library/par.rs +++ b/src/library/par.rs @@ -24,20 +24,18 @@ pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { }); if let Some(Spanned { v, span }) = args.named::<Spanned<Dir>>("dir")? { - if v.axis() == SpecAxis::Horizontal { - dir = Some(v); - } else { + if v.axis() != SpecAxis::Horizontal { bail!(span, "must be horizontal"); } + dir = Some(v); } let mut align = None; if let Some(Spanned { v, span }) = args.named::<Spanned<Align>>("align")? { - if v.axis() == SpecAxis::Horizontal { - align = Some(v); - } else { + if v.axis() != SpecAxis::Horizontal { bail!(span, "must be horizontal"); } + align = Some(v); } ctx.template.modify(move |style| { diff --git a/src/library/placed.rs b/src/library/placed.rs index 0d92fc35..e2c2e9e8 100644 --- a/src/library/placed.rs +++ b/src/library/placed.rs @@ -1,18 +1,13 @@ -use super::parse_aligns; use super::prelude::*; /// `place`: Place content at an absolute position. pub fn place(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - let Spec { x, y } = parse_aligns(args)?; - let dx = args.named("dx")?; - let dy = args.named("dy")?; + let aligns = args.find().unwrap_or(Spec::new(Some(Align::Left), None)); + let offset = Spec::new(args.named("dx")?, args.named("dy")?); let body: Template = args.expect("body")?; Ok(Value::Template(Template::from_block(move |style| { PlacedNode { - child: body - .pack(style) - .moved(dx, dy) - .aligned(Some(x.unwrap_or(Align::Left)), y), + child: body.pack(style).moved(offset).aligned(aligns), } }))) } diff --git a/src/library/shape.rs b/src/library/shape.rs index f47da82f..061b4d25 100644 --- a/src/library/shape.rs +++ b/src/library/shape.rs @@ -5,9 +5,8 @@ use crate::util::RcExt; /// `rect`: A rectangle with optional content. pub fn rect(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - let width = args.named("width")?; - let height = args.named("height")?; - shape_impl(args, ShapeKind::Rect, width, height) + let sizing = Spec::new(args.named("width")?, args.named("height")?); + shape_impl(args, ShapeKind::Rect, sizing) } /// `square`: A square with optional content. @@ -21,14 +20,14 @@ pub fn square(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { None => args.named("height")?, size => size, }; - shape_impl(args, ShapeKind::Square, width, height) + let sizing = Spec::new(width, height); + shape_impl(args, ShapeKind::Square, sizing) } /// `ellipse`: An ellipse with optional content. pub fn ellipse(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - let width = args.named("width")?; - let height = args.named("height")?; - shape_impl(args, ShapeKind::Ellipse, width, height) + let sizing = Spec::new(args.named("width")?, args.named("height")?); + shape_impl(args, ShapeKind::Ellipse, sizing) } /// `circle`: A circle with optional content. @@ -42,14 +41,14 @@ pub fn circle(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { None => args.named("height")?, diameter => diameter, }; - shape_impl(args, ShapeKind::Circle, width, height) + let sizing = Spec::new(width, height); + shape_impl(args, ShapeKind::Circle, sizing) } fn shape_impl( args: &mut Args, kind: ShapeKind, - width: Option<Linear>, - height: Option<Linear>, + sizing: Spec<Option<Linear>>, ) -> TypResult<Value> { // The default appearance of a shape. let default = Stroke { @@ -67,7 +66,10 @@ fn shape_impl( }), }; + // Shorthand for padding. let padding = Sides::splat(args.named("padding")?.unwrap_or_default()); + + // The shape's contents. let body = args.find::<Template>(); Ok(Value::Template(Template::from_inline(move |style| { @@ -78,7 +80,7 @@ fn shape_impl( child: body.as_ref().map(|body| body.pack(style).padded(padding)), } .pack() - .sized(width, height) + .sized(sizing) }))) } diff --git a/src/library/sized.rs b/src/library/sized.rs index 3089f90b..8d69afac 100644 --- a/src/library/sized.rs +++ b/src/library/sized.rs @@ -2,21 +2,19 @@ use super::prelude::*; /// `box`: Size content and place it into a paragraph. pub fn box_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - let width = args.named("width")?; - let height = args.named("height")?; + let sizing = Spec::new(args.named("width")?, args.named("height")?); let body: Template = args.find().unwrap_or_default(); Ok(Value::Template(Template::from_inline(move |style| { - body.pack(style).sized(width, height) + body.pack(style).sized(sizing) }))) } /// `block`: Size content and place it into the flow. pub fn block(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - let width = args.named("width")?; - let height = args.named("height")?; + let sizing = Spec::new(args.named("width")?, args.named("height")?); let body: Template = args.find().unwrap_or_default(); Ok(Value::Template(Template::from_block(move |style| { - body.pack(style).sized(width, height) + body.pack(style).sized(sizing) }))) } diff --git a/src/library/transform.rs b/src/library/transform.rs index 10752358..7553bef2 100644 --- a/src/library/transform.rs +++ b/src/library/transform.rs @@ -2,11 +2,10 @@ use super::prelude::*; /// `move`: Move content without affecting layout. pub fn move_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - let dx = args.named("dx")?; - let dy = args.named("dy")?; + let offset = Spec::new(args.named("x")?, args.named("y")?); let body: Template = args.expect("body")?; Ok(Value::Template(Template::from_inline(move |style| { - body.pack(style).moved(dx, dy) + body.pack(style).moved(offset) }))) } diff --git a/tests/typ/elements/circle.typ b/tests/typ/elements/circle.typ index 9590ef7b..2e97e985 100644 --- a/tests/typ/elements/circle.typ +++ b/tests/typ/elements/circle.typ @@ -10,12 +10,12 @@ Auto-sized circle. \ #circle(fill: rgb("eb5278"), thickness: 2pt, - align(center, horizon)[But, soft!] + align(center + horizon)[But, soft!] ) Center-aligned rect in auto-sized circle. #circle(fill: forest, stroke: conifer, - align(center, horizon, + align(center + horizon, rect(fill: conifer, pad(5pt)[But, soft!]) ) ) @@ -37,7 +37,7 @@ Expanded by height. --- // Test relative sizing. -#let centered(body) = align(center, horizon, body) +#let centered(body) = align(center + horizon, body) #font(fill: white) #rect(width: 100pt, height: 50pt, fill: rgb("aaa"), centered[ #circle(radius: 10pt, fill: eastern, centered[A]) // D=20pt diff --git a/tests/typ/elements/ellipse.typ b/tests/typ/elements/ellipse.typ index 4f1d3187..47f6d0e1 100644 --- a/tests/typ/elements/ellipse.typ +++ b/tests/typ/elements/ellipse.typ @@ -9,7 +9,7 @@ Rect in ellipse in fixed rect. \ #rect(width: 3cm, height: 2cm, fill: rgb("2a631a"), ellipse(fill: forest, rect(fill: conifer, - align(center, horizon)[ + align(center + horizon)[ Stuff inside an ellipse! ] ) diff --git a/tests/typ/elements/image.typ b/tests/typ/elements/image.typ index 84260a28..9d192b40 100644 --- a/tests/typ/elements/image.typ +++ b/tests/typ/elements/image.typ @@ -21,7 +21,7 @@ #image("../../res/tiger.jpg", width: 100%, height: 20pt, fit: "stretch") // Make sure the bounding-box of the image is correct. -#align(bottom, right, image("../../res/tiger.jpg", width: 40pt)) +#align(bottom + right, image("../../res/tiger.jpg", width: 40pt)) --- // Test all three fit modes. diff --git a/tests/typ/layout/align.typ b/tests/typ/layout/align.typ index c41e0cb4..4a50d3dc 100644 --- a/tests/typ/layout/align.typ +++ b/tests/typ/layout/align.typ @@ -7,7 +7,7 @@ align(center, square(size: 20pt, fill: eastern)), align(right, square(size: 15pt, fill: eastern)), ) -#align(center, horizon, rect(fill: eastern, height: 10pt)) +#align(center + horizon, rect(fill: eastern, height: 10pt)) #align(bottom, stack( align(center, rect(fill: conifer, height: 10pt)), rect(fill: forest, height: 10pt), @@ -19,3 +19,17 @@ Dolor ] + +--- +// Ref: false +#test(type(center), "alignment") +#test(type(horizon), "alignment") +#test(type(center + horizon), "2d alignment") + +--- +// Error: 8-22 cannot add two horizontal alignments +#align(center + right, [A]) + +--- +// Error: 8-20 cannot add two vertical alignments +#align(top + bottom, [A]) diff --git a/tests/typ/layout/background.typ b/tests/typ/layout/background.typ index 81c8c99d..6303a83b 100644 --- a/tests/typ/layout/background.typ +++ b/tests/typ/layout/background.typ @@ -13,6 +13,6 @@ height: 100% + 20pt, ) ) -#align(bottom, right)[ +#align(bottom + right)[ _Welcome to_ #underline[*Tigerland*] ] diff --git a/tests/typ/layout/move.typ b/tests/typ/layout/move.typ index a7cdf368..c1f97e15 100644 --- a/tests/typ/layout/move.typ +++ b/tests/typ/layout/move.typ @@ -2,7 +2,7 @@ #let tex = [{ [T] h(-0.14 * size) - move(dy: 0.22 * size)[E] + move(y: 0.22 * size)[E] h(-0.12 * size) [X] }] diff --git a/tests/typ/layout/page.typ b/tests/typ/layout/page.typ index 1f707327..dc6c7c81 100644 --- a/tests/typ/layout/page.typ +++ b/tests/typ/layout/page.typ @@ -9,8 +9,8 @@ // Set all margins at once. [ #page(margins: 5pt) - #place(top, left)[TL] - #place(bottom, right)[BR] + #place(top + left)[TL] + #place(bottom + right)[BR] ] // Set individual margins. diff --git a/tests/typ/layout/placed.typ b/tests/typ/layout/placed.typ index 41431dee..da56ae2d 100644 --- a/tests/typ/layout/placed.typ +++ b/tests/typ/layout/placed.typ @@ -1,5 +1,5 @@ #page("a8") -#place(bottom, center)[© Typst] +#place(bottom + center)[© Typst] = Placement #place(right, image("../../res/tiger.jpg", width: 1.8cm)) diff --git a/tests/typ/text/par.typ b/tests/typ/text/par.typ index 9955b993..7b6bd289 100644 --- a/tests/typ/text/par.typ +++ b/tests/typ/text/par.typ @@ -15,3 +15,7 @@ It is the east, and Juliet is the sun. --- // Error: 13-16 must be horizontal #par(align: top) + +--- +// Error: 13-29 expected alignment, found 2d alignment +#par(align: horizon + center) |
