typst/library/src/layout/transform.rs
2023-04-23 14:33:56 +02:00

196 lines
5.3 KiB
Rust

use typst::geom::Transform;
use crate::prelude::*;
/// Move content without affecting layout.
///
/// The `move` function allows you to move content while the layout still 'sees'
/// it at the original positions. Containers will still be sized as if the content
/// was not moved.
///
/// ## Example
/// ```example
/// #rect(inset: 0pt, move(
/// dx: 6pt, dy: 6pt,
/// rect(
/// inset: 8pt,
/// fill: white,
/// stroke: black,
/// [Abra cadabra]
/// )
/// ))
/// ```
///
/// Display: Move
/// Category: layout
#[element(Layout)]
pub struct MoveElem {
/// The horizontal displacement of the content.
pub dx: Rel<Length>,
/// The vertical displacement of the content.
pub dy: Rel<Length>,
/// The content to move.
#[required]
pub body: Content,
}
impl Layout for MoveElem {
#[tracing::instrument(name = "MoveElem::layout", skip_all)]
fn layout(
&self,
vt: &mut Vt,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
let pod = Regions::one(regions.base(), Axes::splat(false));
let mut frame = self.body().layout(vt, styles, pod)?.into_frame();
let delta = Axes::new(self.dx(styles), self.dy(styles)).resolve(styles);
let delta = delta.zip(regions.base()).map(|(d, s)| d.relative_to(s));
frame.translate(delta.to_point());
Ok(Fragment::frame(frame))
}
}
/// Rotate content without affecting layout.
///
/// Rotate an element by a given angle. The layout will act as if the element
/// was not rotated.
///
/// ## Example
/// ```example
/// #stack(
/// dir: ltr,
/// spacing: 1fr,
/// ..range(16)
/// .map(i => rotate(24deg * i)[X]),
/// )
/// ```
///
/// Display: Rotate
/// Category: layout
#[element(Layout)]
pub struct RotateElem {
/// The amount of rotation.
///
/// ```example
/// #rotate(-1.571rad)[Space!]
/// ```
///
#[positional]
pub angle: Angle,
/// The origin of the rotation.
///
/// By default, the origin is the center of the rotated element. If,
/// however, you wanted the bottom left corner of the rotated element to
/// stay aligned with the baseline, you would set the origin to `bottom +
/// left`.
///
/// ```example
/// #set text(spacing: 8pt)
/// #let square = square.with(width: 8pt)
///
/// #box(square())
/// #box(rotate(30deg, origin: center, square()))
/// #box(rotate(30deg, origin: top + left, square()))
/// #box(rotate(30deg, origin: bottom + right, square()))
/// ```
#[resolve]
pub origin: Axes<Option<GenAlign>>,
/// The content to rotate.
#[required]
pub body: Content,
}
impl Layout for RotateElem {
#[tracing::instrument(name = "RotateElem::layout", skip_all)]
fn layout(
&self,
vt: &mut Vt,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
let pod = Regions::one(regions.base(), Axes::splat(false));
let mut frame = self.body().layout(vt, styles, pod)?.into_frame();
let origin = self.origin(styles).unwrap_or(Align::CENTER_HORIZON);
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
let ts = Transform::translate(x, y)
.pre_concat(Transform::rotate(self.angle(styles)))
.pre_concat(Transform::translate(-x, -y));
frame.transform(ts);
Ok(Fragment::frame(frame))
}
}
/// Scale content without affecting layout.
///
/// The `scale` function allows you to scale and mirror content without
/// affecting the layout.
///
///
/// ## Example
/// ```example
/// #set align(center)
/// #scale(x: -100%)[This is mirrored.]
/// ```
///
/// Display: Scale
/// Category: layout
#[element(Layout)]
pub struct ScaleElem {
/// The horizontal scaling factor.
///
/// The body will be mirrored horizontally if the parameter is negative.
#[parse(
let all = args.find()?;
args.named("x")?.or(all)
)]
#[default(Ratio::one())]
pub x: Ratio,
/// 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,
/// The origin of the transformation.
///
/// By default, the origin is the center of the scaled element.
///
/// ```example
/// A#box(scale(75%)[A])A \
/// B#box(scale(75%, origin: bottom + left)[B])B
/// ```
#[resolve]
pub origin: Axes<Option<GenAlign>>,
/// The content to scale.
#[required]
pub body: Content,
}
impl Layout for ScaleElem {
#[tracing::instrument(name = "ScaleElem::layout", skip_all)]
fn layout(
&self,
vt: &mut Vt,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
let pod = Regions::one(regions.base(), Axes::splat(false));
let mut frame = self.body().layout(vt, styles, pod)?.into_frame();
let origin = self.origin(styles).unwrap_or(Align::CENTER_HORIZON);
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
let transform = Transform::translate(x, y)
.pre_concat(Transform::scale(self.x(styles), self.y(styles)))
.pre_concat(Transform::translate(-x, -y));
frame.transform(transform);
Ok(Fragment::frame(frame))
}
}