summaryrefslogtreecommitdiff
path: root/crates
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 /crates
parent799eb8004eeafd758ed53c79c4e1ce34afb268dd (diff)
Add a skew function (#4803)
Diffstat (limited to 'crates')
-rw-r--r--crates/typst/src/layout/mod.rs1
-rw-r--r--crates/typst/src/layout/transform.rs109
2 files changed, 110 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()