//! Mathematical formulas. #[macro_use] mod ctx; mod accent; mod align; mod attach; mod delimited; mod frac; mod fragment; mod matrix; mod op; mod root; mod row; mod spacing; mod stretch; mod style; mod underover; pub use self::accent::*; pub use self::align::*; pub use self::attach::*; pub use self::delimited::*; pub use self::frac::*; pub use self::matrix::*; pub use self::op::*; pub use self::root::*; pub use self::style::*; pub use self::underover::*; use ttf_parser::{GlyphId, Rect}; use typst::font::Font; use typst::model::{Guard, Module, Scope, SequenceNode, StyledNode}; use unicode_math_class::MathClass; use self::ctx::*; use self::fragment::*; use self::row::*; use self::spacing::*; use crate::layout::{HNode, ParNode, Spacing}; use crate::prelude::*; use crate::text::{ families, variant, FallbackList, FontFamily, LinebreakNode, SpaceNode, TextNode, TextSize, }; /// Create a module with all math definitions. pub fn module() -> Module { let mut math = Scope::deduplicating(); math.def_func::("formula"); math.def_func::("text"); // Grouping. math.def_func::("lr"); math.def_func::("abs"); math.def_func::("norm"); math.def_func::("floor"); math.def_func::("ceil"); // Attachments and accents. math.def_func::("attach"); math.def_func::("scripts"); math.def_func::("limits"); math.def_func::("accent"); math.def_func::("underline"); math.def_func::("overline"); math.def_func::("underbrace"); math.def_func::("overbrace"); math.def_func::("underbracket"); math.def_func::("overbracket"); // Fractions and matrix-likes. math.def_func::("frac"); math.def_func::("binom"); math.def_func::("vec"); math.def_func::("mat"); math.def_func::("cases"); // Roots. math.def_func::("sqrt"); math.def_func::("root"); // Styles. math.def_func::("upright"); math.def_func::("bold"); math.def_func::("italic"); math.def_func::("serif"); math.def_func::("sans"); math.def_func::("cal"); math.def_func::("frak"); math.def_func::("mono"); math.def_func::("bb"); // Text operators. math.def_func::("op"); op::define(&mut math); // Spacings. spacing::define(&mut math); // Symbols. for (name, symbol) in crate::symbols::SYM { math.define(*name, symbol.clone()); } Module::new("math").with_scope(math) } /// # Formula /// A mathematical formula. /// /// Can be displayed inline with text or as a separate block. /// /// ## Example /// ``` /// #set text("Latin Modern Roman") /// /// Let $a$, $b$, and $c$ be the side /// lengths of right-angled triangle. /// Then, we know that: /// $ a^2 + b^2 = c^2 $ /// /// Prove by induction: /// $ sum_(k=1)^n k = (n(n+1)) / 2 $ /// ``` /// /// ## Syntax /// This function also has dedicated syntax: Write mathematical markup within /// dollar signs to create a formula. Starting and ending the formula with at /// least one space lifts it into a separate block that is centered /// horizontally. For more details about math syntax, see the /// [main math page](/docs/reference/math). /// /// ## Parameters /// - body: Content (positional, required) /// The contents of the formula. /// /// - block: bool (named) /// Whether the formula is displayed as a separate block. /// /// ## Category /// math #[func] #[capable(Show, Finalize, Layout, Inline, LayoutMath)] #[derive(Debug, Clone, Hash)] pub struct FormulaNode { /// Whether the formula is displayed as a separate block. pub block: bool, /// The content of the formula. pub body: Content, } #[node] impl FormulaNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let body = args.expect("body")?; let block = args.named("block")?.unwrap_or(false); Ok(Self { block, body }.pack()) } fn field(&self, name: &str) -> Option { match name { "body" => Some(Value::Content(self.body.clone())), "block" => Some(Value::Bool(self.block)), _ => None, } } } impl Show for FormulaNode { fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult { let mut realized = self.clone().pack().guarded(Guard::Base(NodeId::of::())); if self.block { realized = realized.aligned(Axes::with_x(Some(Align::Center.into()))) } Ok(realized) } } impl Finalize for FormulaNode { fn finalize(&self, realized: Content) -> Content { realized.styled( TextNode::FAMILY, FallbackList(vec![FontFamily::new("New Computer Modern Math")]), ) } } impl Layout for FormulaNode { fn layout( &self, vt: &mut Vt, styles: StyleChain, regions: Regions, ) -> SourceResult { // Find a math font. let variant = variant(styles); let world = vt.world(); let Some(font) = families(styles) .find_map(|family| { let id = world.book().select(family, variant)?; let font = world.font(id)?; let _ = font.ttf().tables().math?.constants?; Some(font) }) else { if let Some(span) = self.body.span() { bail!(span, "current font does not support math"); } return Ok(Fragment::frame(Frame::new(Size::zero()))) }; let mut ctx = MathContext::new(vt, styles, regions, &font, self.block); let mut frame = ctx.layout_frame(self)?; if !self.block { let slack = styles.get(ParNode::LEADING) * 0.7; let top_edge = styles.get(TextNode::TOP_EDGE).resolve(styles, font.metrics()); let bottom_edge = -styles.get(TextNode::BOTTOM_EDGE).resolve(styles, font.metrics()); let ascent = top_edge.max(frame.ascent() - slack); let descent = bottom_edge.max(frame.descent() - slack); frame.translate(Point::with_y(ascent - frame.baseline())); frame.size_mut().y = ascent + descent; } Ok(Fragment::frame(frame)) } } impl Inline for FormulaNode {} #[capability] pub trait LayoutMath { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()>; } impl LayoutMath for FormulaNode { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { self.body.layout_math(ctx) } } impl LayoutMath for Content { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { if let Some(node) = self.to::() { for child in &node.0 { child.layout_math(ctx)?; } return Ok(()); } if let Some(styled) = self.to::() { let prev_map = std::mem::replace(&mut ctx.map, styled.map.clone()); let prev_size = ctx.size; ctx.map.apply(prev_map.clone()); ctx.size = ctx.styles().get(TextNode::SIZE); styled.sub.layout_math(ctx)?; ctx.size = prev_size; ctx.map = prev_map; return Ok(()); } if self.is::() { ctx.push(MathFragment::Space(ctx.space_width.scaled(ctx))); return Ok(()); } if self.is::() { ctx.push(MathFragment::Linebreak); return Ok(()); } if let Some(node) = self.to::() { if let Spacing::Relative(rel) = node.amount { if rel.rel.is_zero() { ctx.push(MathFragment::Spacing(rel.abs.resolve(ctx.styles()))); } } return Ok(()); } if let Some(node) = self.to::() { ctx.layout_text(&node.0)?; return Ok(()); } if let Some(node) = self.with::() { return node.layout_math(ctx); } let frame = ctx.layout_content(self)?; ctx.push(FrameFragment::new(ctx, frame).with_spaced(true)); Ok(()) } }