mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Add a skew function (#4803)
This commit is contained in:
parent
799eb8004e
commit
95740ac2ab
@ -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>();
|
||||
|
@ -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()
|
||||
|
BIN
tests/ref/transform-skew-both-axes.png
Normal file
BIN
tests/ref/transform-skew-both-axes.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
tests/ref/transform-skew-origin.png
Normal file
BIN
tests/ref/transform-skew-origin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 487 B |
BIN
tests/ref/transform-skew-relative-sizing.png
Normal file
BIN
tests/ref/transform-skew-relative-sizing.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 819 B |
BIN
tests/ref/transform-skew.png
Normal file
BIN
tests/ref/transform-skew.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 878 B |
@ -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]!
|
||||
|
Loading…
x
Reference in New Issue
Block a user