mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Allow absolute lengths in scale
(#4271)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
parent
993e7a45a9
commit
df56a2d20d
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
BIN
tests/ref/transform-scale-abs-and-auto.png
Normal file
BIN
tests/ref/transform-scale-abs-and-auto.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user