From 95740ac2ab350a194949251febe758914072d803 Mon Sep 17 00:00:00 2001 From: Bzero Date: Mon, 2 Sep 2024 14:56:08 +0200 Subject: [PATCH] Add a skew function (#4803) --- crates/typst/src/layout/mod.rs | 1 + crates/typst/src/layout/transform.rs | 109 +++++++++++++++++++ tests/ref/transform-skew-both-axes.png | Bin 0 -> 1679 bytes tests/ref/transform-skew-origin.png | Bin 0 -> 487 bytes tests/ref/transform-skew-relative-sizing.png | Bin 0 -> 819 bytes tests/ref/transform-skew.png | Bin 0 -> 878 bytes tests/suite/layout/transform.typ | 50 +++++++++ 7 files changed, 160 insertions(+) create mode 100644 tests/ref/transform-skew-both-axes.png create mode 100644 tests/ref/transform-skew-origin.png create mode 100644 tests/ref/transform-skew-relative-sizing.png create mode 100644 tests/ref/transform-skew.png diff --git a/crates/typst/src/layout/mod.rs b/crates/typst/src/layout/mod.rs index 8f3dc7c56..e75bfc5ed 100644 --- a/crates/typst/src/layout/mod.rs +++ b/crates/typst/src/layout/mod.rs @@ -104,6 +104,7 @@ pub fn define(global: &mut Scope) { global.define_elem::(); global.define_elem::(); global.define_elem::(); + global.define_elem::(); global.define_elem::(); global.define_func::(); global.define_func::(); diff --git a/crates/typst/src/layout/transform.rs b/crates/typst/src/layout/transform.rs index 2c718193f..d4010a9ed 100644 --- a/crates/typst/src/layout/transform.rs +++ b/crates/typst/src/layout/transform.rs @@ -335,6 +335,106 @@ cast! { length: Length => ScaleAmount::Length(length), } +/// Skews content. +/// +/// Skews an element in horizontal and/or vertical direction. The layout will +/// act as if the element was not skewed unless you specify `{reflow: true}`. +/// +/// # Example +/// ```example +/// #skew(ax: -12deg)[This is some fake italic text.] +/// ``` +#[elem(Show)] +pub struct SkewElem { + /// The horizontal skewing angle. + /// + /// ```example + /// #skew(ax: 30deg)[Skewed] + /// ``` + /// + #[default(Angle::zero())] + pub ax: Angle, + + /// The vertical skewing angle. + /// + /// ```example + /// #skew(ay: 30deg)[Skewed] + /// ``` + /// + #[default(Angle::zero())] + pub ay: Angle, + + /// The origin of the skew transformation. + /// + /// The origin will stay fixed during the operation. + /// + /// ```example + /// X #box(skew(ax: -30deg, origin: center + horizon)[X]) X \ + /// X #box(skew(ax: -30deg, origin: bottom + left)[X]) X \ + /// X #box(skew(ax: -30deg, origin: top + right)[X]) X + /// ``` + #[fold] + #[default(HAlignment::Center + VAlignment::Horizon)] + pub origin: Alignment, + + /// Whether the skew transformation impacts the layout. + /// + /// If set to `{false}`, the skewed content will retain the bounding box of + /// the original content. If set to `{true}`, the bounding box will take the + /// transformation of the content into account and adjust the layout accordingly. + /// + /// ```example + /// Hello #skew(ay: 30deg, reflow: true, "World")! + /// ``` + #[default(false)] + pub reflow: bool, + + /// The content to skew. + #[required] + pub body: Content, +} + +impl Show for Packed { + fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult { + Ok(BlockElem::single_layouter(self.clone(), layout_skew) + .pack() + .spanned(self.span())) + } +} + +/// Layout the skewed content. +#[typst_macros::time(span = elem.span())] +fn layout_skew( + elem: &Packed, + engine: &mut Engine, + locator: Locator, + styles: StyleChain, + region: Region, +) -> SourceResult { + let ax = elem.ax(styles); + let ay = elem.ay(styles); + let align = elem.origin(styles).resolve(styles); + + // Compute the new region's approximate size. + let size = if region.size.is_finite() { + compute_bounding_box(region.size, Transform::skew(ax, ay)).1 + } else { + Size::splat(Abs::inf()) + }; + + measure_and_layout( + engine, + locator, + region, + size, + styles, + elem.body(), + Transform::skew(ax, ay), + align, + elem.reflow(styles), + ) +} + /// A scale-skew-translate transformation. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct Transform { @@ -382,6 +482,15 @@ impl Transform { } } + /// A skew transform. + pub fn skew(ax: Angle, ay: Angle) -> Self { + Self { + kx: Ratio::new(ax.tan()), + ky: Ratio::new(ay.tan()), + ..Self::identity() + } + } + /// Whether this is the identity transformation. pub fn is_identity(self) -> bool { self == Self::identity() diff --git a/tests/ref/transform-skew-both-axes.png b/tests/ref/transform-skew-both-axes.png new file mode 100644 index 0000000000000000000000000000000000000000..da9cf5ebd3cb27f10a4dc966844ee4ad948d7077 GIT binary patch literal 1679 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 literal 0 HcmV?d00001 diff --git a/tests/suite/layout/transform.typ b/tests/suite/layout/transform.typ index 3604b72f7..fde5edfd9 100644 --- a/tests/suite/layout/transform.typ +++ b/tests/suite/layout/transform.typ @@ -115,3 +115,53 @@ Hello #scaled[World]! #scale(x: auto, y: 50pt, reflow: true, cylinder) #scale(x: 100pt, y: auto, reflow: true, cylinder) #scale(x: 150%, y: auto, reflow: true, cylinder) + +--- transform-skew --- +// Test skewing along one axis. +#set page(width: 100pt, height: 60pt) +#set text(size: 12pt) +#let skewed(body) = box(skew(ax: -30deg, body)) + +#set skew(reflow: false) +Hello #skewed[World]! + +#set skew(reflow: true) +Hello #skewed[World]! + +--- transform-skew-both-axes --- +// Test skewing along both axes. +#set page(width: 100pt, height: 250pt) +#set text(size: 12pt) +#let skewed(angle) = box(skew(ax: 30deg, ay: angle)[Some Text]) + +#set skew(reflow: true) +#for angle in range(-30, 31, step: 10) { + skewed(angle * 1deg) +} + +--- transform-skew-origin --- +// Test setting skewing origin. +#set page(width: 100pt, height:40pt) +#set text(spacing: 20pt) +#let square = square.with(width: 8pt) +#let skew-square(origin) = box(place(square(stroke: gray)) + + place(skew(ax: -30deg, ay: -30deg, origin: origin, square()))) +#skew-square(center+horizon) +#skew-square(bottom+left) +#skew-square(top+right) +#skew-square(horizon+right) + +--- transform-skew-relative-sizing --- +// Test relative sizing in skewed boxes. +#set page(width: 100pt, height: 60pt) +#set text(size: 12pt) +#let skewed(body) = box(skew( + ax: 30deg, + box(stroke: 0.5pt, width: 30%, clip: true, body) +)) + +#set skew(reflow: false) +Hello #skewed[World]!\ + +#set skew(reflow: true) +Hello #skewed[World]!