diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-01-22 13:28:24 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-01-22 13:28:24 +0100 |
| commit | f7b3b30ca1b678683a40830406af981ad6b197c2 (patch) | |
| tree | 8eb7829f62e8c22757ce6c079590f230054ac8e1 /library/src/math/root.rs | |
| parent | ea378e89b4f2267bb85ec56c905111a6c73d4721 (diff) | |
Math root layout
Diffstat (limited to 'library/src/math/root.rs')
| -rw-r--r-- | library/src/math/root.rs | 157 |
1 files changed, 146 insertions, 11 deletions
diff --git a/library/src/math/root.rs b/library/src/math/root.rs index 90664afa..f5c5b2b8 100644 --- a/library/src/math/root.rs +++ b/library/src/math/root.rs @@ -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) +} |
