diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index c1e352d1b..9e692f571 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -39,6 +39,9 @@ use self::fragment::*; use self::row::*; use self::spacing::*; use crate::layout::{HNode, ParNode, Spacing}; +use crate::meta::{ + Count, Counter, CounterAction, CounterNode, CounterUpdate, LocalName, Numbering, +}; use crate::prelude::*; use crate::text::{ families, variant, FontFamily, FontList, LinebreakNode, SpaceNode, TextNode, TextSize, @@ -132,17 +135,37 @@ pub fn module() -> Module { /// /// Display: Equation /// Category: math -#[node(Show, Finalize, Layout, LayoutMath)] +#[node(Locatable, Synthesize, Show, Finalize, Layout, LayoutMath, Count, LocalName)] pub struct EquationNode { /// Whether the equation is displayed as a separate block. #[default(false)] pub block: bool, + /// How to [number]($func/numbering) block-level equations. + /// + /// ```example + /// #set math.equation(numbering: "(1)") + /// + /// We define: + /// $ phi.alt := (1 + sqrt(5)) / 2 $ + /// + /// With @ratio, we get: + /// $ F_n = floor(1 / sqrt(5) phi.alt^n) $ + /// ``` + pub numbering: Option, + /// The contents of the equation. #[required] pub body: Content, } +impl Synthesize for EquationNode { + fn synthesize(&mut self, _: &Vt, styles: StyleChain) { + self.push_block(self.block(styles)); + self.push_numbering(self.numbering(styles)); + } +} + impl Show for EquationNode { fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { let mut realized = self.clone().pack().guarded(Guard::Base(NodeId::of::())); @@ -170,6 +193,8 @@ impl Layout for EquationNode { styles: StyleChain, regions: Regions, ) -> SourceResult { + const NUMBER_GUTTER: Em = Em::new(0.5); + let block = self.block(styles); // Find a math font. @@ -189,7 +214,34 @@ impl Layout for EquationNode { let mut ctx = MathContext::new(vt, styles, regions, &font, block); let mut frame = ctx.layout_frame(self)?; - if !block { + if block { + if let Some(numbering) = self.numbering(styles) { + let pod = Regions::one(regions.base(), Axes::splat(false)); + let counter = CounterNode::new( + Counter::of(Self::id()), + CounterAction::Get(numbering), + ); + + let sub = counter.pack().layout(vt, styles, pod)?.into_frame(); + let width = if regions.size.x.is_finite() { + regions.size.x + } else { + frame.width() + 2.0 * (sub.width() + NUMBER_GUTTER.resolve(styles)) + }; + + let height = frame.height().max(sub.height()); + frame.resize(Size::new(width, height), Align::CENTER_HORIZON); + + let x = if TextNode::dir_in(styles).is_positive() { + frame.width() - sub.width() + } else { + Abs::zero() + }; + let y = (frame.height() - sub.height()) / 2.0; + + frame.push_frame(Point::new(x, y), sub) + } + } else { let slack = ParNode::leading_in(styles) * 0.7; let top_edge = TextNode::top_edge_in(styles).resolve(styles, font.metrics()); let bottom_edge = @@ -205,6 +257,23 @@ impl Layout for EquationNode { } } +impl Count for EquationNode { + fn update(&self) -> Option { + (self.block(StyleChain::default()) + && self.numbering(StyleChain::default()).is_some()) + .then(|| CounterUpdate::Step(NonZeroUsize::ONE)) + } +} + +impl LocalName for EquationNode { + fn local_name(&self, lang: Lang) -> &'static str { + match lang { + Lang::GERMAN => "Gleichung", + Lang::ENGLISH | _ => "Equation", + } + } +} + pub trait LayoutMath { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()>; } diff --git a/tests/ref/math/numbering.png b/tests/ref/math/numbering.png new file mode 100644 index 000000000..3b9db84f1 Binary files /dev/null and b/tests/ref/math/numbering.png differ diff --git a/tests/typ/math/numbering.typ b/tests/typ/math/numbering.typ new file mode 100644 index 000000000..71049a09d --- /dev/null +++ b/tests/typ/math/numbering.typ @@ -0,0 +1,11 @@ +// Test equation numbering. + +--- +#set page(width: 150pt) +#set math.equation(numbering: "(I)") + +We define $x$ in preparation of @fib: +$ phi.alt := (1 + sqrt(5)) / 2 $ + +With @ratio, we get +$ F_n = floor(1 / sqrt(5) phi.alt^n) $