Add a skew function (#4803)

This commit is contained in:
Bzero 2024-09-02 14:56:08 +02:00 committed by GitHub
parent 799eb8004e
commit 95740ac2ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 160 additions and 0 deletions

View File

@ -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>();

View File

@ -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()

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 878 B

View File

@ -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]!