From c7f4d6b12ee3138c752402889eeaa603ac69351d Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sat, 18 Mar 2023 19:28:03 +0100 Subject: [PATCH] Equation numbering --- library/src/math/mod.rs | 73 ++++++++++++++++++++++++++++++++++- tests/ref/math/numbering.png | Bin 0 -> 13751 bytes tests/typ/math/numbering.typ | 11 ++++++ 3 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 tests/ref/math/numbering.png create mode 100644 tests/typ/math/numbering.typ 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 0000000000000000000000000000000000000000..3b9db84f18a91f8882a66d4c00a3eafc48bf8ab3 GIT binary patch literal 13751 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 kk&o-ZbN + +With @ratio, we get +$ F_n = floor(1 / sqrt(5) phi.alt^n) $