summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBzero <lolo-b@posteo.net>2024-09-02 14:56:08 +0200
committerGitHub <noreply@github.com>2024-09-02 12:56:08 +0000
commit95740ac2ab350a194949251febe758914072d803 (patch)
treed5cc5f5000a8cc7f9ff53f6cbea5e3de6f0a5394
parent799eb8004eeafd758ed53c79c4e1ce34afb268dd (diff)
Add a skew function (#4803)
-rw-r--r--crates/typst/src/layout/mod.rs1
-rw-r--r--crates/typst/src/layout/transform.rs109
-rw-r--r--tests/ref/transform-skew-both-axes.pngbin0 -> 1679 bytes
-rw-r--r--tests/ref/transform-skew-origin.pngbin0 -> 487 bytes
-rw-r--r--tests/ref/transform-skew-relative-sizing.pngbin0 -> 819 bytes
-rw-r--r--tests/ref/transform-skew.pngbin0 -> 878 bytes
-rw-r--r--tests/suite/layout/transform.typ50
7 files changed, 160 insertions, 0 deletions
diff --git a/crates/typst/src/layout/mod.rs b/crates/typst/src/layout/mod.rs
index 8f3dc7c5..e75bfc5e 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::<MoveElem>();
global.define_elem::<ScaleElem>();
global.define_elem::<RotateElem>();
+ global.define_elem::<SkewElem>();
global.define_elem::<HideElem>();
global.define_func::<measure>();
global.define_func::<layout>();
diff --git a/crates/typst/src/layout/transform.rs b/crates/typst/src/layout/transform.rs
index 2c718193..d4010a9e 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<SkewElem> {
+ fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ 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<SkewElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ region: Region,
+) -> SourceResult<Frame> {
+ 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 00000000..da9cf5eb
--- /dev/null
+++ b/tests/ref/transform-skew-both-axes.png
Binary files differ
diff --git a/tests/ref/transform-skew-origin.png b/tests/ref/transform-skew-origin.png
new file mode 100644
index 00000000..4f4c4b08
--- /dev/null
+++ b/tests/ref/transform-skew-origin.png
Binary files differ
diff --git a/tests/ref/transform-skew-relative-sizing.png b/tests/ref/transform-skew-relative-sizing.png
new file mode 100644
index 00000000..41496d17
--- /dev/null
+++ b/tests/ref/transform-skew-relative-sizing.png
Binary files differ
diff --git a/tests/ref/transform-skew.png b/tests/ref/transform-skew.png
new file mode 100644
index 00000000..76bee82a
--- /dev/null
+++ b/tests/ref/transform-skew.png
Binary files differ
diff --git a/tests/suite/layout/transform.typ b/tests/suite/layout/transform.typ
index 3604b72f..fde5edfd 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]!