mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Implement math cancel
function (#793)
This commit is contained in:
parent
4cea7007d0
commit
a4075f8b9b
169
library/src/math/cancel.rs
Normal file
169
library/src/math/cancel.rs
Normal file
@ -0,0 +1,169 @@
|
||||
use super::*;
|
||||
|
||||
/// Displays a diagonal line over a part of an equation.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```example
|
||||
/// Here, we can simplify:
|
||||
/// $ (a dot.c b dot.c cancel(x)) / cancel(x) $
|
||||
/// ```
|
||||
///
|
||||
/// Display: Cancel
|
||||
/// Category: math
|
||||
#[element(LayoutMath)]
|
||||
pub struct CancelElem {
|
||||
/// The content over which the line should be placed.
|
||||
#[required]
|
||||
pub body: Content,
|
||||
|
||||
/// The length of the line, relative to the length of the diagonal spanning
|
||||
/// the whole element being "cancelled". A value of `{100%}` would then have
|
||||
/// the line span precisely the element's diagonal.
|
||||
///
|
||||
/// Defaults to `{100% + 3pt}`.
|
||||
///
|
||||
/// ```example
|
||||
/// $ a + cancel(x, length: #200%) - b - cancel(x, length: #200%) $
|
||||
/// ```
|
||||
#[default(Rel::new(Ratio::one(), Abs::pt(3.0).into()))]
|
||||
pub length: Rel<Length>,
|
||||
|
||||
/// If the cancel line should be inverted (heading northwest instead of
|
||||
/// northeast).
|
||||
///
|
||||
/// Defaults to `{false}`.
|
||||
///
|
||||
/// ```example
|
||||
/// $ (a cancel((b + c), inverted: #true)) / cancel(b + c, inverted: #true) $
|
||||
/// ```
|
||||
#[default(false)]
|
||||
pub inverted: bool,
|
||||
|
||||
/// If two opposing cancel lines should be drawn, forming a cross over the
|
||||
/// element. Overrides `inverted`.
|
||||
///
|
||||
/// Defaults to `{false}`.
|
||||
///
|
||||
/// ```example
|
||||
/// $ cancel(x, cross: #true) $
|
||||
/// ```
|
||||
#[default(false)]
|
||||
pub cross: bool,
|
||||
|
||||
/// Rotate the cancel line by a certain angle. See the
|
||||
/// [line's documentation]($func/line.angle) for more details.
|
||||
///
|
||||
/// ```example
|
||||
/// $ cancel(x, rotation: #30deg) $
|
||||
/// ```
|
||||
#[default(Angle::zero())]
|
||||
pub rotation: Angle,
|
||||
|
||||
/// How to stroke the cancel line. See the
|
||||
/// [line's documentation]($func/line.stroke) for more details.
|
||||
///
|
||||
/// ```example
|
||||
/// $ cancel(x, stroke: #{red + 1.5pt}) $
|
||||
/// ```
|
||||
#[resolve]
|
||||
#[fold]
|
||||
pub stroke: PartialStroke,
|
||||
}
|
||||
|
||||
impl LayoutMath for CancelElem {
|
||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||
let mut body = ctx.layout_frame(&self.body())?;
|
||||
|
||||
let styles = ctx.styles();
|
||||
let body_size = body.size();
|
||||
let span = self.span();
|
||||
let length = self.length(styles).resolve(styles);
|
||||
|
||||
// Default stroke has 0.5pt for better visuals.
|
||||
let stroke = self.stroke(styles).unwrap_or(Stroke {
|
||||
paint: TextElem::fill_in(styles),
|
||||
thickness: Abs::pt(0.5),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let invert = self.inverted(styles);
|
||||
let cross = self.cross(styles);
|
||||
let angle = self.rotation(styles);
|
||||
|
||||
let invert_first_line = !cross && invert;
|
||||
let first_line = draw_cancel_line(
|
||||
length,
|
||||
stroke.clone(),
|
||||
invert_first_line,
|
||||
angle,
|
||||
body_size,
|
||||
span,
|
||||
);
|
||||
|
||||
// The origin of our line is the very middle of the element.
|
||||
let center = body_size.to_point() / 2.0;
|
||||
body.push_frame(center, first_line);
|
||||
|
||||
if cross {
|
||||
// Draw the second line.
|
||||
let second_line =
|
||||
draw_cancel_line(length, stroke, true, angle, body_size, span);
|
||||
|
||||
body.push_frame(center, second_line);
|
||||
}
|
||||
|
||||
ctx.push(FrameFragment::new(ctx, body));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws a cancel line.
|
||||
fn draw_cancel_line(
|
||||
length: Rel<Abs>,
|
||||
stroke: Stroke,
|
||||
invert: bool,
|
||||
angle: Angle,
|
||||
body_size: Size,
|
||||
span: Span,
|
||||
) -> Frame {
|
||||
// B
|
||||
// /|
|
||||
// diagonal / | height
|
||||
// / |
|
||||
// / |
|
||||
// O ----
|
||||
// width
|
||||
let diagonal = body_size.to_point().hypot();
|
||||
let length = length.relative_to(diagonal);
|
||||
let (width, height) = (body_size.x, body_size.y);
|
||||
let mid = body_size / 2.0;
|
||||
|
||||
// Scale the amount needed such that the cancel line has the given 'length'
|
||||
// (reference length, or 100%, is the whole diagonal).
|
||||
// Scales from the center.
|
||||
let scale = length.to_raw() / diagonal.to_raw();
|
||||
|
||||
// invert horizontally if 'invert' was given
|
||||
let scale_x = scale * if invert { -1.0 } else { 1.0 };
|
||||
let scale_y = scale;
|
||||
let scales = Axes::new(scale_x, scale_y);
|
||||
|
||||
// Draw a line from bottom left to top right of the given element, where the
|
||||
// origin represents the very middle of that element, that is, a line from
|
||||
// (-width / 2, height / 2) with length components (width, -height) (sign is
|
||||
// inverted in the y-axis). After applying the scale, the line will have the
|
||||
// correct length and orientation (inverted if needed).
|
||||
let start = Axes::new(-mid.x, mid.y).zip(scales).map(|(l, s)| l * s);
|
||||
let delta = Axes::new(width, -height).zip(scales).map(|(l, s)| l * s);
|
||||
|
||||
let mut frame = Frame::new(body_size);
|
||||
frame.push(
|
||||
start.to_point(),
|
||||
FrameItem::Shape(Geometry::Line(delta.to_point()).stroked(stroke), span),
|
||||
);
|
||||
|
||||
// Having the middle of the line at the origin is convenient here.
|
||||
frame.transform(Transform::rotate(angle));
|
||||
frame
|
||||
}
|
@ -5,6 +5,7 @@ mod ctx;
|
||||
mod accent;
|
||||
mod align;
|
||||
mod attach;
|
||||
mod cancel;
|
||||
mod delimited;
|
||||
mod frac;
|
||||
mod fragment;
|
||||
@ -20,6 +21,7 @@ mod underover;
|
||||
pub use self::accent::*;
|
||||
pub use self::align::*;
|
||||
pub use self::attach::*;
|
||||
pub use self::cancel::*;
|
||||
pub use self::delimited::*;
|
||||
pub use self::frac::*;
|
||||
pub use self::matrix::*;
|
||||
@ -71,6 +73,7 @@ pub fn module() -> Module {
|
||||
math.define("overbrace", OverbraceElem::func());
|
||||
math.define("underbracket", UnderbracketElem::func());
|
||||
math.define("overbracket", OverbracketElem::func());
|
||||
math.define("cancel", CancelElem::func());
|
||||
|
||||
// Fractions and matrix-likes.
|
||||
math.define("frac", FracElem::func());
|
||||
|
@ -45,6 +45,11 @@ impl Point {
|
||||
Self { x: self.x.max(other.x), y: self.y.max(other.y) }
|
||||
}
|
||||
|
||||
/// The distance between this point and the origin.
|
||||
pub fn hypot(self) -> Abs {
|
||||
Abs::raw(self.x.to_raw().hypot(self.y.to_raw()))
|
||||
}
|
||||
|
||||
/// Transform the point with the given transformation.
|
||||
pub fn transform(self, ts: Transform) -> Self {
|
||||
Self::new(
|
||||
|
BIN
tests/ref/math/cancel.png
Normal file
BIN
tests/ref/math/cancel.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
34
tests/typ/math/cancel.typ
Normal file
34
tests/typ/math/cancel.typ
Normal file
@ -0,0 +1,34 @@
|
||||
// Tests the cancel() function.
|
||||
|
||||
---
|
||||
// Inline
|
||||
$a + 5 + cancel(x) + b - cancel(x)$
|
||||
|
||||
$c + (a dot.c cancel(b dot.c c))/(cancel(b dot.c c))$
|
||||
|
||||
---
|
||||
// Display
|
||||
#set page(width: auto)
|
||||
$ a + b + cancel(b + c) - cancel(b) - cancel(c) - 5 + cancel(6) - cancel(6) $
|
||||
$ e + (a dot.c cancel((b + c + d)))/(cancel(b + c + d)) $
|
||||
|
||||
---
|
||||
// Inverted
|
||||
$a + cancel(x, inverted: #true) - cancel(x, inverted: #true) + 10 + cancel(y) - cancel(y)$
|
||||
$ x + cancel("abcdefg", inverted: #true) $
|
||||
|
||||
---
|
||||
// Cross
|
||||
$a + cancel(b + c + d, cross: #true, stroke: #red) + e$
|
||||
$ a + cancel(b + c + d, cross: #true) + e $
|
||||
|
||||
---
|
||||
// Resized and styled
|
||||
#set page(width: 200pt, height: auto)
|
||||
$a + cancel(x, length: #200%) - cancel(x, length: #50%, stroke: #{red + 1.1pt})$
|
||||
$ b + cancel(x, length: #150%) - cancel(a + b + c, length: #50%, stroke: #{blue + 1.2pt}) $
|
||||
|
||||
---
|
||||
// Rotated
|
||||
$x + cancel(y, rotation: #90deg) - cancel(z, rotation: #135deg)$
|
||||
$ e + cancel((j + e)/(f + e)) - cancel((j + e)/(f + e), rotation: #30deg) $
|
Loading…
x
Reference in New Issue
Block a user