mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +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::<MoveElem>();
|
||||||
global.define_elem::<ScaleElem>();
|
global.define_elem::<ScaleElem>();
|
||||||
global.define_elem::<RotateElem>();
|
global.define_elem::<RotateElem>();
|
||||||
|
global.define_elem::<SkewElem>();
|
||||||
global.define_elem::<HideElem>();
|
global.define_elem::<HideElem>();
|
||||||
global.define_func::<measure>();
|
global.define_func::<measure>();
|
||||||
global.define_func::<layout>();
|
global.define_func::<layout>();
|
||||||
|
@ -335,6 +335,106 @@ cast! {
|
|||||||
length: Length => ScaleAmount::Length(length),
|
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.
|
/// A scale-skew-translate transformation.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct Transform {
|
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.
|
/// Whether this is the identity transformation.
|
||||||
pub fn is_identity(self) -> bool {
|
pub fn is_identity(self) -> bool {
|
||||||
self == Self::identity()
|
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: auto, y: 50pt, reflow: true, cylinder)
|
||||||
#scale(x: 100pt, y: auto, reflow: true, cylinder)
|
#scale(x: 100pt, y: auto, reflow: true, cylinder)
|
||||||
#scale(x: 150%, 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