Allow absolute lengths in scale (#4271)

Co-authored-by: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
+merlan #flirora 2024-07-17 04:27:46 -04:00 committed by GitHub
parent 993e7a45a9
commit df56a2d20d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 99 additions and 15 deletions

View File

@ -70,7 +70,7 @@ impl Ratio {
impl Debug for Ratio { impl Debug for Ratio {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{:?}%", self.get()) write!(f, "{:?}%", self.get() * 100.0)
} }
} }

View File

@ -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::engine::Engine;
use crate::foundations::{ 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::introspection::Locator;
use crate::layout::{ use crate::layout::{
@ -188,15 +192,15 @@ pub struct ScaleElem {
let all = args.find()?; let all = args.find()?;
args.named("x")?.or(all) args.named("x")?.or(all)
)] )]
#[default(Ratio::one())] #[default(Smart::Custom(ScaleAmount::Ratio(Ratio::one())))]
pub x: Ratio, pub x: Smart<ScaleAmount>,
/// The vertical scaling factor. /// The vertical scaling factor.
/// ///
/// The body will be mirrored vertically if the parameter is negative. /// The body will be mirrored vertically if the parameter is negative.
#[parse(args.named("y")?.or(all))] #[parse(args.named("y")?.or(all))]
#[default(Ratio::one())] #[default(Smart::Custom(ScaleAmount::Ratio(Ratio::one())))]
pub y: Ratio, pub y: Smart<ScaleAmount>,
/// The origin of the transformation. /// The origin of the transformation.
/// ///
@ -242,12 +246,9 @@ fn layout_scale(
styles: StyleChain, styles: StyleChain,
region: Region, region: Region,
) -> SourceResult<Frame> { ) -> 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. // 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( measure_and_layout(
engine, engine,
@ -256,12 +257,84 @@ fn layout_scale(
size, size,
styles, styles,
elem.body(), elem.body(),
Transform::scale(sx, sy), Transform::scale(scale.x, scale.y),
align, elem.origin(styles).resolve(styles),
elem.reflow(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. /// A scale-skew-translate transformation.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Transform { pub struct Transform {

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -74,7 +74,7 @@ Hello #rotated[World]!\
Hello #rotated[World]! Hello #rotated[World]!
--- transform-scale --- --- transform-scale ---
// Test that scaling impact layout. // Test that scaling impacts layout.
#set page(width: 200pt) #set page(width: 200pt)
#set text(size: 32pt) #set text(size: 32pt)
#let scaled(body) = box(scale( #let scaled(body) = box(scale(
@ -104,3 +104,14 @@ Hello #scaled[World]!\
#set scale(reflow: true) #set scale(reflow: true)
Hello #scaled[World]! 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)