summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/typst/src/layout/ratio.rs2
-rw-r--r--crates/typst/src/layout/transform.rs99
-rw-r--r--tests/ref/transform-scale-abs-and-auto.pngbin0 -> 3719 bytes
-rw-r--r--tests/suite/layout/transform.typ13
4 files changed, 99 insertions, 15 deletions
diff --git a/crates/typst/src/layout/ratio.rs b/crates/typst/src/layout/ratio.rs
index 2d791f2d..020d689a 100644
--- a/crates/typst/src/layout/ratio.rs
+++ b/crates/typst/src/layout/ratio.rs
@@ -70,7 +70,7 @@ impl Ratio {
impl Debug for Ratio {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(f, "{:?}%", self.get())
+ write!(f, "{:?}%", self.get() * 100.0)
}
}
diff --git a/crates/typst/src/layout/transform.rs b/crates/typst/src/layout/transform.rs
index 7172466f..ad3668d1 100644
--- a/crates/typst/src/layout/transform.rs
+++ b/crates/typst/src/layout/transform.rs
@@ -1,7 +1,11 @@
-use crate::diag::SourceResult;
+use std::ops::Div;
+
+use once_cell::unsync::Lazy;
+
+use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
- elem, Content, NativeElement, Packed, Resolve, Show, StyleChain,
+ cast, elem, Content, NativeElement, Packed, Resolve, Show, Smart, StyleChain,
};
use crate::introspection::Locator;
use crate::layout::{
@@ -188,15 +192,15 @@ pub struct ScaleElem {
let all = args.find()?;
args.named("x")?.or(all)
)]
- #[default(Ratio::one())]
- pub x: Ratio,
+ #[default(Smart::Custom(ScaleAmount::Ratio(Ratio::one())))]
+ pub x: Smart<ScaleAmount>,
/// The vertical scaling factor.
///
/// The body will be mirrored vertically if the parameter is negative.
#[parse(args.named("y")?.or(all))]
- #[default(Ratio::one())]
- pub y: Ratio,
+ #[default(Smart::Custom(ScaleAmount::Ratio(Ratio::one())))]
+ pub y: Smart<ScaleAmount>,
/// The origin of the transformation.
///
@@ -242,12 +246,9 @@ fn layout_scale(
styles: StyleChain,
region: Region,
) -> SourceResult<Frame> {
- let sx = elem.x(styles);
- let sy = elem.y(styles);
- let align = elem.origin(styles).resolve(styles);
-
// Compute the new region's approximate size.
- let size = region.size.zip_map(Axes::new(sx, sy), |r, s| s.of(r)).map(Abs::abs);
+ let scale = elem.resolve_scale(engine, locator.relayout(), region.size, styles)?;
+ let size = region.size.zip_map(scale, |r, s| s.of(r)).map(Abs::abs);
measure_and_layout(
engine,
@@ -256,12 +257,84 @@ fn layout_scale(
size,
styles,
elem.body(),
- Transform::scale(sx, sy),
- align,
+ Transform::scale(scale.x, scale.y),
+ elem.origin(styles).resolve(styles),
elem.reflow(styles),
)
}
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+enum ScaleAmount {
+ Ratio(Ratio),
+ Length(Length),
+}
+
+impl Packed<ScaleElem> {
+ /// Resolves scale parameters, preserving aspect ratio if one of the scales is set to `auto`.
+ fn resolve_scale(
+ &self,
+ engine: &mut Engine,
+ locator: Locator,
+ container: Size,
+ styles: StyleChain,
+ ) -> SourceResult<Axes<Ratio>> {
+ fn resolve_axis(
+ axis: Smart<ScaleAmount>,
+ body: impl Fn() -> SourceResult<Abs>,
+ styles: StyleChain,
+ ) -> SourceResult<Smart<Ratio>> {
+ Ok(match axis {
+ Smart::Auto => Smart::Auto,
+ Smart::Custom(amt) => Smart::Custom(match amt {
+ ScaleAmount::Ratio(ratio) => ratio,
+ ScaleAmount::Length(length) => {
+ let length = length.resolve(styles);
+ Ratio::new(length.div(body()?))
+ }
+ }),
+ })
+ }
+
+ let size = Lazy::new(|| {
+ let pod = Regions::one(container, Axes::splat(false));
+ let frame = self.body().layout(engine, locator, styles, pod)?.into_frame();
+ SourceResult::Ok(frame.size())
+ });
+
+ let x = resolve_axis(
+ self.x(styles),
+ || size.as_ref().map(|size| size.x).map_err(Clone::clone),
+ styles,
+ )?;
+
+ let y = resolve_axis(
+ self.y(styles),
+ || size.as_ref().map(|size| size.y).map_err(Clone::clone),
+ styles,
+ )?;
+
+ match (x, y) {
+ (Smart::Auto, Smart::Auto) => {
+ bail!(self.span(), "x and y cannot both be auto")
+ }
+ (Smart::Custom(x), Smart::Custom(y)) => Ok(Axes::new(x, y)),
+ (Smart::Auto, Smart::Custom(v)) | (Smart::Custom(v), Smart::Auto) => {
+ Ok(Axes::splat(v))
+ }
+ }
+ }
+}
+
+cast! {
+ ScaleAmount,
+ self => match self {
+ ScaleAmount::Ratio(ratio) => ratio.into_value(),
+ ScaleAmount::Length(length) => length.into_value(),
+ },
+ ratio: Ratio => ScaleAmount::Ratio(ratio),
+ length: Length => ScaleAmount::Length(length),
+}
+
/// A scale-skew-translate transformation.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Transform {
diff --git a/tests/ref/transform-scale-abs-and-auto.png b/tests/ref/transform-scale-abs-and-auto.png
new file mode 100644
index 00000000..9b50396d
--- /dev/null
+++ b/tests/ref/transform-scale-abs-and-auto.png
Binary files differ
diff --git a/tests/suite/layout/transform.typ b/tests/suite/layout/transform.typ
index 50a6d417..3604b72f 100644
--- a/tests/suite/layout/transform.typ
+++ b/tests/suite/layout/transform.typ
@@ -74,7 +74,7 @@ Hello #rotated[World]!\
Hello #rotated[World]!
--- transform-scale ---
-// Test that scaling impact layout.
+// Test that scaling impacts layout.
#set page(width: 200pt)
#set text(size: 32pt)
#let scaled(body) = box(scale(
@@ -104,3 +104,14 @@ Hello #scaled[World]!\
#set scale(reflow: true)
Hello #scaled[World]!
+
+--- transform-scale-abs-and-auto ---
+// Test scaling by absolute lengths and auto.
+#set page(width: 200pt, height: 200pt)
+#let cylinder = image("/assets/images/cylinder.svg")
+
+#cylinder
+#scale(x: 100pt, y: 50pt, reflow: true, cylinder)
+#scale(x: auto, y: 50pt, reflow: true, cylinder)
+#scale(x: 100pt, y: auto, reflow: true, cylinder)
+#scale(x: 150%, y: auto, reflow: true, cylinder)