mirror of
https://github.com/typst/typst
synced 2025-05-13 12:36:23 +08:00
Math root layout
This commit is contained in:
parent
ea378e89b4
commit
f7b3b30ca1
@ -48,6 +48,7 @@ pub fn define(scope: &mut Scope) {
|
||||
scope.def_func::<FracNode>("frac");
|
||||
scope.def_func::<ScriptNode>("script");
|
||||
scope.def_func::<SqrtNode>("sqrt");
|
||||
scope.def_func::<RootNode>("root");
|
||||
scope.def_func::<VecNode>("vec");
|
||||
scope.def_func::<CasesNode>("cases");
|
||||
scope.def_func::<BoldNode>("bold");
|
||||
|
@ -3,36 +3,171 @@ use super::*;
|
||||
/// # Square Root
|
||||
/// A square root.
|
||||
///
|
||||
/// _Note:_ Non-square roots are not yet supported.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```
|
||||
/// $ sqrt(x^2) = x = sqrt(x)^2 $
|
||||
/// ```
|
||||
///
|
||||
/// ## Parameters
|
||||
/// - body: Content (positional, required)
|
||||
/// - radicand: Content (positional, required)
|
||||
/// The expression to take the square root of.
|
||||
///
|
||||
/// ## Category
|
||||
/// math
|
||||
#[func]
|
||||
#[capable(Texify)]
|
||||
#[capable(LayoutMath)]
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct SqrtNode(pub Content);
|
||||
|
||||
#[node]
|
||||
impl SqrtNode {
|
||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||
Ok(Self(args.expect("body")?).pack())
|
||||
Ok(Self(args.expect("radicand")?).pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl Texify for SqrtNode {
|
||||
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
|
||||
t.push_str("\\sqrt{");
|
||||
self.0.texify(t)?;
|
||||
t.push_str("}");
|
||||
Ok(())
|
||||
impl LayoutMath for SqrtNode {
|
||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||
layout(ctx, None, &self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// # Root
|
||||
/// A general root.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```
|
||||
/// $ radical(3, x) $
|
||||
/// ```
|
||||
///
|
||||
/// ## Parameters
|
||||
/// - index: Content (positional, required)
|
||||
/// Which root of the radicand to take.
|
||||
///
|
||||
/// - radicand: Content (positional, required)
|
||||
/// The expression to take the root of.
|
||||
///
|
||||
/// ## Category
|
||||
/// math
|
||||
#[func]
|
||||
#[capable(LayoutMath)]
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct RootNode {
|
||||
index: Content,
|
||||
radicand: Content,
|
||||
}
|
||||
|
||||
#[node]
|
||||
impl RootNode {
|
||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||
Ok(Self {
|
||||
index: args.expect("index")?,
|
||||
radicand: args.expect("radicand")?,
|
||||
}
|
||||
.pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl LayoutMath for RootNode {
|
||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||
layout(ctx, Some(&self.index), &self.radicand)
|
||||
}
|
||||
}
|
||||
|
||||
/// Layout a root.
|
||||
///
|
||||
/// https://www.w3.org/TR/mathml-core/#radicals-msqrt-mroot
|
||||
fn layout(
|
||||
ctx: &mut MathContext,
|
||||
mut index: Option<&Content>,
|
||||
radicand: &Content,
|
||||
) -> SourceResult<()> {
|
||||
let gap = scaled!(
|
||||
ctx,
|
||||
text: radical_vertical_gap,
|
||||
display: radical_display_style_vertical_gap,
|
||||
);
|
||||
let thickness = scaled!(ctx, radical_rule_thickness);
|
||||
let ascender = scaled!(ctx, radical_extra_ascender);
|
||||
let kern_before = scaled!(ctx, radical_kern_before_degree);
|
||||
let kern_after = scaled!(ctx, radical_kern_after_degree);
|
||||
let raise = percent!(ctx, radical_degree_bottom_raise_percent);
|
||||
|
||||
// Layout radicand.
|
||||
ctx.style(ctx.style.with_cramped(true));
|
||||
let radicand = ctx.layout_frame(radicand)?;
|
||||
ctx.unstyle();
|
||||
|
||||
// Layout root symbol.
|
||||
let target = radicand.height() + thickness + gap;
|
||||
let sqrt = precomposed(ctx, index, target)
|
||||
.map(|frame| {
|
||||
index = None;
|
||||
frame
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
let glyph = GlyphFragment::new(ctx, '√');
|
||||
glyph.stretch_vertical(ctx, target, Abs::zero()).frame
|
||||
});
|
||||
|
||||
// Layout the index.
|
||||
let mut offset = Abs::zero();
|
||||
let index = if let Some(index) = index {
|
||||
// Script-script style looks too small, we use Script style instead.
|
||||
ctx.style(ctx.style.with_size(MathSize::Script));
|
||||
let frame = ctx.layout_frame(index)?;
|
||||
offset = kern_before + frame.width() + kern_after;
|
||||
ctx.unstyle();
|
||||
frame
|
||||
} else {
|
||||
Frame::new(Size::zero())
|
||||
};
|
||||
|
||||
let width = offset + sqrt.width() + radicand.width();
|
||||
let height = sqrt.height() + ascender;
|
||||
let size = Size::new(width, height);
|
||||
let remains = (sqrt.height() - radicand.height() - thickness) / 2.0;
|
||||
let index_pos =
|
||||
Point::new(kern_before, height - index.ascent() - raise * sqrt.height());
|
||||
let sqrt_pos = Point::new(offset, ascender);
|
||||
let line_pos = Point::new(offset + sqrt.width(), ascender + thickness / 2.0);
|
||||
let line_length = radicand.width();
|
||||
let radicand_pos =
|
||||
Point::new(offset + sqrt.width(), ascender + thickness + gap.max(remains));
|
||||
let baseline = radicand_pos.y + radicand.ascent();
|
||||
|
||||
let mut frame = Frame::new(size);
|
||||
frame.set_baseline(baseline);
|
||||
frame.push_frame(index_pos, index);
|
||||
frame.push_frame(sqrt_pos, sqrt);
|
||||
frame.push(
|
||||
line_pos,
|
||||
Element::Shape(
|
||||
Geometry::Line(Point::with_x(line_length))
|
||||
.stroked(Stroke { paint: ctx.fill, thickness }),
|
||||
),
|
||||
);
|
||||
frame.push_frame(radicand_pos, radicand);
|
||||
ctx.push(frame);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Select a precomposed radical, if the font has it.
|
||||
fn precomposed(ctx: &MathContext, index: Option<&Content>, target: Abs) -> Option<Frame> {
|
||||
let node = index?.to::<MathNode>()?.body.to::<AtomNode>()?;
|
||||
let c = match node.0.as_str() {
|
||||
"3" => '∛',
|
||||
"4" => '∜',
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
ctx.ttf.glyph_index(c)?;
|
||||
let glyph = GlyphFragment::new(ctx, c);
|
||||
let variant = glyph.stretch_vertical(ctx, target, Abs::zero()).frame;
|
||||
if variant.height() < target {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(variant)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user