summaryrefslogtreecommitdiff
path: root/library/src/math
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-07-02 19:59:52 +0200
committerLaurenz <laurmaedje@gmail.com>2023-07-02 20:07:43 +0200
commitebfdb1dafa430786db10dad2ef7d5467c1bdbed1 (patch)
tree2bbc24ddb4124c4bb14dec0e536129d4de37b056 /library/src/math
parent3ab19185093d7709f824b95b979060ce125389d8 (diff)
Move everything into `crates/` directory
Diffstat (limited to 'library/src/math')
-rw-r--r--library/src/math/accent.rs139
-rw-r--r--library/src/math/align.rs63
-rw-r--r--library/src/math/attach.rs411
-rw-r--r--library/src/math/cancel.rs187
-rw-r--r--library/src/math/ctx.rs268
-rw-r--r--library/src/math/delimited.rs200
-rw-r--r--library/src/math/frac.rs148
-rw-r--r--library/src/math/fragment.rs414
-rw-r--r--library/src/math/matrix.rs313
-rw-r--r--library/src/math/mod.rs480
-rw-r--r--library/src/math/op.rs113
-rw-r--r--library/src/math/root.rs156
-rw-r--r--library/src/math/row.rs258
-rw-r--r--library/src/math/spacing.rs60
-rw-r--r--library/src/math/stretch.rs199
-rw-r--r--library/src/math/style.rs620
-rw-r--r--library/src/math/underover.rs339
17 files changed, 0 insertions, 4368 deletions
diff --git a/library/src/math/accent.rs b/library/src/math/accent.rs
deleted file mode 100644
index d1bee198..00000000
--- a/library/src/math/accent.rs
+++ /dev/null
@@ -1,139 +0,0 @@
-use super::*;
-
-/// How much the accent can be shorter than the base.
-const ACCENT_SHORT_FALL: Em = Em::new(0.5);
-
-/// Attaches an accent to a base.
-///
-/// ## Example { #example }
-/// ```example
-/// $grave(a) = accent(a, `)$ \
-/// $arrow(a) = accent(a, arrow)$ \
-/// $tilde(a) = accent(a, \u{0303})$
-/// ```
-///
-/// Display: Accent
-/// Category: math
-#[element(LayoutMath)]
-pub struct AccentElem {
- /// The base to which the accent is applied.
- /// May consist of multiple letters.
- ///
- /// ```example
- /// $arrow(A B C)$
- /// ```
- #[required]
- pub base: Content,
-
- /// The accent to apply to the base.
- ///
- /// Supported accents include:
- ///
- /// | Accent | Name | Codepoint |
- /// | ------------- | --------------- | --------- |
- /// | Grave | `grave` | <code>&DiacriticalGrave;</code> |
- /// | Acute | `acute` | `´` |
- /// | Circumflex | `hat` | `^` |
- /// | Tilde | `tilde` | `~` |
- /// | Macron | `macron` | `¯` |
- /// | Breve | `breve` | `˘` |
- /// | Dot | `dot` | `.` |
- /// | Double dot | `dot.double` | `¨` |
- /// | Triple dot | `dot.triple` | <code>&tdot;</code> |
- /// | Quadruple dot | `dot.quad` | <code>&DotDot;</code> |
- /// | Diaeresis | `diaer` | `¨` |
- /// | Circle | `circle` | `∘` |
- /// | Double acute | `acute.double` | `˝` |
- /// | Caron | `caron` | `ˇ` |
- /// | Right arrow | `arrow`, `->` | `→` |
- /// | Left arrow | `arrow.l`, `<-` | `←` |
- #[required]
- pub accent: Accent,
-}
-
-impl LayoutMath for AccentElem {
- #[tracing::instrument(skip(ctx))]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- ctx.style(ctx.style.with_cramped(true));
- let base = ctx.layout_fragment(&self.base())?;
- ctx.unstyle();
-
- // Preserve class to preserve automatic spacing.
- let base_class = base.class().unwrap_or(MathClass::Normal);
- let base_attach = match &base {
- MathFragment::Glyph(base) => {
- attachment(ctx, base.id, base.italics_correction)
- }
- _ => (base.width() + base.italics_correction()) / 2.0,
- };
-
- // Forcing the accent to be at least as large as the base makes it too
- // wide in many case.
- let Accent(c) = self.accent();
- let glyph = GlyphFragment::new(ctx, c, self.span());
- let short_fall = ACCENT_SHORT_FALL.scaled(ctx);
- let variant = glyph.stretch_horizontal(ctx, base.width(), short_fall);
- let accent = variant.frame;
- let accent_attach = match variant.id {
- Some(id) => attachment(ctx, id, variant.italics_correction),
- None => accent.width() / 2.0,
- };
-
- // Descent is negative because the accent's ink bottom is above the
- // baseline. Therefore, the default gap is the accent's negated descent
- // minus the accent base height. Only if the base is very small, we need
- // a larger gap so that the accent doesn't move too low.
- let accent_base_height = scaled!(ctx, accent_base_height);
- let gap = -accent.descent() - base.height().min(accent_base_height);
- let size = Size::new(base.width(), accent.height() + gap + base.height());
- let accent_pos = Point::with_x(base_attach - accent_attach);
- let base_pos = Point::with_y(accent.height() + gap);
- let base_ascent = base.ascent();
- let baseline = base_pos.y + base.ascent();
-
- let mut frame = Frame::new(size);
- frame.set_baseline(baseline);
- frame.push_frame(accent_pos, accent);
- frame.push_frame(base_pos, base.into_frame());
- ctx.push(
- FrameFragment::new(ctx, frame)
- .with_class(base_class)
- .with_base_ascent(base_ascent),
- );
-
- Ok(())
- }
-}
-
-/// The horizontal attachment position for the given glyph.
-fn attachment(ctx: &MathContext, id: GlyphId, italics_correction: Abs) -> Abs {
- ctx.table
- .glyph_info
- .and_then(|info| info.top_accent_attachments)
- .and_then(|attachments| attachments.get(id))
- .map(|record| record.value.scaled(ctx))
- .unwrap_or_else(|| {
- let advance = ctx.ttf.glyph_hor_advance(id).unwrap_or_default();
- (advance.scaled(ctx) + italics_correction) / 2.0
- })
-}
-
-/// An accent character.
-pub struct Accent(char);
-
-impl Accent {
- /// Normalize a character into an accent.
- pub fn new(c: char) -> Self {
- Self(Symbol::combining_accent(c).unwrap_or(c))
- }
-}
-
-cast! {
- Accent,
- self => self.0.into_value(),
- v: char => Self::new(v),
- v: Content => match v.to::<TextElem>() {
- Some(elem) => Value::Str(elem.text().into()).cast()?,
- None => bail!("expected text"),
- },
-}
diff --git a/library/src/math/align.rs b/library/src/math/align.rs
deleted file mode 100644
index aee89a89..00000000
--- a/library/src/math/align.rs
+++ /dev/null
@@ -1,63 +0,0 @@
-use super::*;
-
-/// A math alignment point: `&`, `&&`.
-///
-/// Display: Alignment Point
-/// Category: math
-#[element(LayoutMath)]
-pub struct AlignPointElem {}
-
-impl LayoutMath for AlignPointElem {
- #[tracing::instrument(skip(ctx))]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- ctx.push(MathFragment::Align);
- Ok(())
- }
-}
-
-pub(super) struct AlignmentResult {
- pub points: Vec<Abs>,
- pub width: Abs,
-}
-
-/// Determine the position of the alignment points.
-pub(super) fn alignments(rows: &[MathRow]) -> AlignmentResult {
- let mut widths = Vec::<Abs>::new();
-
- let mut pending_width = Abs::zero();
- for row in rows {
- let mut width = Abs::zero();
- let mut alignment_index = 0;
-
- for fragment in row.iter() {
- if matches!(fragment, MathFragment::Align) {
- if alignment_index < widths.len() {
- widths[alignment_index].set_max(width);
- } else {
- widths.push(width.max(pending_width));
- }
- width = Abs::zero();
- alignment_index += 1;
- } else {
- width += fragment.width();
- }
- }
- if widths.is_empty() {
- pending_width.set_max(width);
- } else if alignment_index < widths.len() {
- widths[alignment_index].set_max(width);
- } else {
- widths.push(width.max(pending_width));
- }
- }
-
- let mut points = widths;
- for i in 1..points.len() {
- let prev = points[i - 1];
- points[i] += prev;
- }
- AlignmentResult {
- width: points.last().copied().unwrap_or(pending_width),
- points,
- }
-}
diff --git a/library/src/math/attach.rs b/library/src/math/attach.rs
deleted file mode 100644
index fedeb908..00000000
--- a/library/src/math/attach.rs
+++ /dev/null
@@ -1,411 +0,0 @@
-use super::*;
-
-/// A base with optional attachments.
-///
-/// ## Example { #example }
-/// ```example
-/// // With syntax.
-/// $ sum_(i=0)^n a_i = 2^(1+i) $
-///
-/// // With function call.
-/// $ attach(
-/// Pi, t: alpha, b: beta,
-/// tl: 1, tr: 2+3, bl: 4+5, br: 6,
-/// ) $
-/// ```
-///
-/// ## Syntax { #syntax }
-/// This function also has dedicated syntax for attachments after the base: Use
-/// the underscore (`_`) to indicate a subscript i.e. bottom attachment and the
-/// hat (`^`) to indicate a superscript i.e. top attachment.
-///
-/// Display: Attachment
-/// Category: math
-#[element(LayoutMath)]
-pub struct AttachElem {
- /// The base to which things are attached.
- #[required]
- pub base: Content,
-
- /// The top attachment, smartly positioned at top-right or above the base.
- ///
- /// You can wrap the base in `{limits()}` or `{scripts()}` to override the
- /// smart positioning.
- pub t: Option<Content>,
-
- /// The bottom attachment, smartly positioned at the bottom-right or below
- /// the base.
- ///
- /// You can wrap the base in `{limits()}` or `{scripts()}` to override the
- /// smart positioning.
- pub b: Option<Content>,
-
- /// The top-left attachment (before the base).
- pub tl: Option<Content>,
-
- /// The bottom-left attachment (before base).
- pub bl: Option<Content>,
-
- /// The top-right attachment (after the base).
- pub tr: Option<Content>,
-
- /// The bottom-right attachment (after the base).
- pub br: Option<Content>,
-}
-
-impl LayoutMath for AttachElem {
- #[tracing::instrument(skip(ctx))]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- type GetAttachment = fn(&AttachElem, styles: StyleChain) -> Option<Content>;
- let layout_attachment = |ctx: &mut MathContext, getter: GetAttachment| {
- getter(self, ctx.styles())
- .map(|elem| ctx.layout_fragment(&elem))
- .transpose()
- };
-
- let base = ctx.layout_fragment(&self.base())?;
-
- ctx.style(ctx.style.for_superscript());
- let tl = layout_attachment(ctx, Self::tl)?;
- let tr = layout_attachment(ctx, Self::tr)?;
- let t = layout_attachment(ctx, Self::t)?;
- ctx.unstyle();
-
- ctx.style(ctx.style.for_subscript());
- let bl = layout_attachment(ctx, Self::bl)?;
- let br = layout_attachment(ctx, Self::br)?;
- let b = layout_attachment(ctx, Self::b)?;
- ctx.unstyle();
-
- let limits = base.limits().active(ctx);
- let (t, tr) = if limits || tr.is_some() { (t, tr) } else { (None, t) };
- let (b, br) = if limits || br.is_some() { (b, br) } else { (None, b) };
- layout_attachments(ctx, base, [tl, t, tr, bl, b, br])
- }
-}
-
-/// Forces a base to display attachments as scripts.
-///
-/// ## Example { #example }
-/// ```example
-/// $ scripts(sum)_1^2 != sum_1^2 $
-/// ```
-///
-/// Display: Scripts
-/// Category: math
-#[element(LayoutMath)]
-pub struct ScriptsElem {
- /// The base to attach the scripts to.
- #[required]
- pub body: Content,
-}
-
-impl LayoutMath for ScriptsElem {
- #[tracing::instrument(skip(ctx))]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- let mut fragment = ctx.layout_fragment(&self.body())?;
- fragment.set_limits(Limits::Never);
- ctx.push(fragment);
- Ok(())
- }
-}
-
-/// Forces a base to display attachments as limits.
-///
-/// ## Example { #example }
-/// ```example
-/// $ limits(A)_1^2 != A_1^2 $
-/// ```
-///
-/// Display: Limits
-/// Category: math
-#[element(LayoutMath)]
-pub struct LimitsElem {
- /// The base to attach the limits to.
- #[required]
- pub body: Content,
-
- /// Whether to also force limits in inline equations.
- ///
- /// When applying limits globally (e.g., through a show rule), it is
- /// typically a good idea to disable this.
- #[default(true)]
- pub inline: bool,
-}
-
-impl LayoutMath for LimitsElem {
- #[tracing::instrument(skip(ctx))]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- let mut fragment = ctx.layout_fragment(&self.body())?;
- fragment.set_limits(if self.inline(ctx.styles()) {
- Limits::Always
- } else {
- Limits::Display
- });
- ctx.push(fragment);
- Ok(())
- }
-}
-
-/// Describes in which situation a frame should use limits for attachments.
-#[derive(Debug, Copy, Clone)]
-pub enum Limits {
- /// Always scripts.
- Never,
- /// Display limits only in `display` math.
- Display,
- /// Always limits.
- Always,
-}
-
-impl Limits {
- /// The default limit configuration if the given character is the base.
- pub fn for_char(c: char) -> Self {
- if Self::DEFAULT_TO_LIMITS.contains(&c) {
- Limits::Display
- } else {
- Limits::Never
- }
- }
-
- /// Whether limits should be displayed in this context
- pub fn active(&self, ctx: &MathContext) -> bool {
- match self {
- Self::Always => true,
- Self::Display => ctx.style.size == MathSize::Display,
- Self::Never => false,
- }
- }
-
- /// Unicode codepoints that should show attachments as limits in display
- /// mode.
- #[rustfmt::skip]
- const DEFAULT_TO_LIMITS: &[char] = &[
- /* ∏ */ '\u{220F}', /* ∐ */ '\u{2210}', /* ∑ */ '\u{2211}',
- /* ⋀ */ '\u{22C0}', /* ⋁ */ '\u{22C1}',
- /* ⋂ */ '\u{22C2}', /* ⋃ */ '\u{22C3}',
- /* ⨀ */ '\u{2A00}', /* ⨁ */ '\u{2A01}', /* ⨂ */ '\u{2A02}',
- /* ⨃ */ '\u{2A03}', /* ⨄ */ '\u{2A04}',
- /* ⨅ */ '\u{2A05}', /* ⨆ */ '\u{2A06}',
- ];
-}
-
-macro_rules! measure {
- ($e: ident, $attr: ident) => {
- $e.as_ref().map(|e| e.$attr()).unwrap_or_default()
- };
-}
-
-/// Layout the attachments.
-fn layout_attachments(
- ctx: &mut MathContext,
- base: MathFragment,
- [tl, t, tr, bl, b, br]: [Option<MathFragment>; 6],
-) -> SourceResult<()> {
- let (shift_up, shift_down) =
- compute_shifts_up_and_down(ctx, &base, [&tl, &tr, &bl, &br]);
-
- let sup_delta = Abs::zero();
- let sub_delta = -base.italics_correction();
- let (base_width, base_ascent, base_descent) =
- (base.width(), base.ascent(), base.descent());
- let base_class = base.class().unwrap_or(MathClass::Normal);
-
- let ascent = base_ascent
- .max(shift_up + measure!(tr, ascent))
- .max(shift_up + measure!(tl, ascent))
- .max(shift_up + measure!(t, height));
-
- let descent = base_descent
- .max(shift_down + measure!(br, descent))
- .max(shift_down + measure!(bl, descent))
- .max(shift_down + measure!(b, height));
-
- let pre_sup_width = measure!(tl, width);
- let pre_sub_width = measure!(bl, width);
- let pre_width_dif = pre_sup_width - pre_sub_width; // Could be negative.
- let pre_width_max = pre_sup_width.max(pre_sub_width);
- let post_max_width =
- (sup_delta + measure!(tr, width)).max(sub_delta + measure!(br, width));
-
- let (center_frame, base_offset) = attach_top_and_bottom(ctx, base, t, b);
- let base_pos =
- Point::new(sup_delta + pre_width_max, ascent - base_ascent - base_offset);
- if [&tl, &bl, &tr, &br].iter().all(|&e| e.is_none()) {
- ctx.push(FrameFragment::new(ctx, center_frame).with_class(base_class));
- return Ok(());
- }
-
- let mut frame = Frame::new(Size::new(
- pre_width_max + base_width + post_max_width + scaled!(ctx, space_after_script),
- ascent + descent,
- ));
- frame.set_baseline(ascent);
- frame.push_frame(base_pos, center_frame);
-
- if let Some(tl) = tl {
- let pos =
- Point::new(-pre_width_dif.min(Abs::zero()), ascent - shift_up - tl.ascent());
- frame.push_frame(pos, tl.into_frame());
- }
-
- if let Some(bl) = bl {
- let pos =
- Point::new(pre_width_dif.max(Abs::zero()), ascent + shift_down - bl.ascent());
- frame.push_frame(pos, bl.into_frame());
- }
-
- if let Some(tr) = tr {
- let pos = Point::new(
- sup_delta + pre_width_max + base_width,
- ascent - shift_up - tr.ascent(),
- );
- frame.push_frame(pos, tr.into_frame());
- }
-
- if let Some(br) = br {
- let pos = Point::new(
- sub_delta + pre_width_max + base_width,
- ascent + shift_down - br.ascent(),
- );
- frame.push_frame(pos, br.into_frame());
- }
-
- ctx.push(FrameFragment::new(ctx, frame).with_class(base_class));
-
- Ok(())
-}
-
-fn attach_top_and_bottom(
- ctx: &mut MathContext,
- base: MathFragment,
- t: Option<MathFragment>,
- b: Option<MathFragment>,
-) -> (Frame, Abs) {
- let upper_gap_min = scaled!(ctx, upper_limit_gap_min);
- let upper_rise_min = scaled!(ctx, upper_limit_baseline_rise_min);
- let lower_gap_min = scaled!(ctx, lower_limit_gap_min);
- let lower_drop_min = scaled!(ctx, lower_limit_baseline_drop_min);
-
- let mut base_offset = Abs::zero();
- let mut width = base.width();
- let mut height = base.height();
-
- if let Some(t) = &t {
- let top_gap = upper_gap_min.max(upper_rise_min - t.descent());
- width.set_max(t.width());
- height += t.height() + top_gap;
- base_offset = top_gap + t.height();
- }
-
- if let Some(b) = &b {
- let bottom_gap = lower_gap_min.max(lower_drop_min - b.ascent());
- width.set_max(b.width());
- height += b.height() + bottom_gap;
- }
-
- let base_pos = Point::new((width - base.width()) / 2.0, base_offset);
- let delta = base.italics_correction() / 2.0;
-
- let mut frame = Frame::new(Size::new(width, height));
- frame.set_baseline(base_pos.y + base.ascent());
- frame.push_frame(base_pos, base.into_frame());
-
- if let Some(t) = t {
- let top_pos = Point::with_x((width - t.width()) / 2.0 + delta);
- frame.push_frame(top_pos, t.into_frame());
- }
-
- if let Some(b) = b {
- let bottom_pos =
- Point::new((width - b.width()) / 2.0 - delta, height - b.height());
- frame.push_frame(bottom_pos, b.into_frame());
- }
-
- (frame, base_offset)
-}
-
-fn compute_shifts_up_and_down(
- ctx: &MathContext,
- base: &MathFragment,
- [tl, tr, bl, br]: [&Option<MathFragment>; 4],
-) -> (Abs, Abs) {
- let sup_shift_up = if ctx.style.cramped {
- scaled!(ctx, superscript_shift_up_cramped)
- } else {
- scaled!(ctx, superscript_shift_up)
- };
-
- let sup_bottom_min = scaled!(ctx, superscript_bottom_min);
- let sup_bottom_max_with_sub = scaled!(ctx, superscript_bottom_max_with_subscript);
- let sup_drop_max = scaled!(ctx, superscript_baseline_drop_max);
- let gap_min = scaled!(ctx, sub_superscript_gap_min);
- let sub_shift_down = scaled!(ctx, subscript_shift_down);
- let sub_top_max = scaled!(ctx, subscript_top_max);
- let sub_drop_min = scaled!(ctx, subscript_baseline_drop_min);
-
- let mut shift_up = Abs::zero();
- let mut shift_down = Abs::zero();
- let is_char_box = is_character_box(base);
-
- if tl.is_some() || tr.is_some() {
- let ascent = match &base {
- MathFragment::Frame(frame) => frame.base_ascent,
- _ => base.ascent(),
- };
- shift_up = shift_up
- .max(sup_shift_up)
- .max(if is_char_box { Abs::zero() } else { ascent - sup_drop_max })
- .max(sup_bottom_min + measure!(tl, descent))
- .max(sup_bottom_min + measure!(tr, descent));
- }
-
- if bl.is_some() || br.is_some() {
- shift_down = shift_down
- .max(sub_shift_down)
- .max(if is_char_box { Abs::zero() } else { base.descent() + sub_drop_min })
- .max(measure!(bl, ascent) - sub_top_max)
- .max(measure!(br, ascent) - sub_top_max);
- }
-
- for (sup, sub) in [(tl, bl), (tr, br)] {
- if let (Some(sup), Some(sub)) = (&sup, &sub) {
- let sup_bottom = shift_up - sup.descent();
- let sub_top = sub.ascent() - shift_down;
- let gap = sup_bottom - sub_top;
- if gap >= gap_min {
- continue;
- }
-
- let increase = gap_min - gap;
- let sup_only =
- (sup_bottom_max_with_sub - sup_bottom).clamp(Abs::zero(), increase);
- let rest = (increase - sup_only) / 2.0;
- shift_up += sup_only + rest;
- shift_down += rest;
- }
- }
-
- (shift_up, shift_down)
-}
-
-/// Whether the fragment consists of a single character or atomic piece of text.
-fn is_character_box(fragment: &MathFragment) -> bool {
- match fragment {
- MathFragment::Glyph(_) | MathFragment::Variant(_) => {
- fragment.class() != Some(MathClass::Large)
- }
- MathFragment::Frame(fragment) => is_atomic_text_frame(&fragment.frame),
- _ => false,
- }
-}
-
-/// Handles e.g. "sin", "log", "exp", "CustomOperator".
-fn is_atomic_text_frame(frame: &Frame) -> bool {
- // Meta information isn't visible or renderable, so we exclude it.
- let mut iter = frame
- .items()
- .map(|(_, item)| item)
- .filter(|item| !matches!(item, FrameItem::Meta(_, _)));
- matches!(iter.next(), Some(FrameItem::Text(_))) && iter.next().is_none()
-}
diff --git a/library/src/math/cancel.rs b/library/src/math/cancel.rs
deleted file mode 100644
index f576a727..00000000
--- a/library/src/math/cancel.rs
+++ /dev/null
@@ -1,187 +0,0 @@
-use super::*;
-
-/// Displays a diagonal line over a part of an equation.
-///
-/// This is commonly used to show the elimination of a term.
-///
-/// ## Example { #example }
-/// ```example
-/// >>> #set page(width: 140pt)
-/// Here, we can simplify:
-/// $ (a dot b dot cancel(x)) /
-/// cancel(x) $
-/// ```
-///
-/// Display: Cancel
-/// Category: math
-#[element(LayoutMath)]
-pub struct CancelElem {
- /// The content over which the line should be placed.
- #[required]
- pub body: Content,
-
- /// The length of the line, relative to the length of the diagonal spanning
- /// the whole element being "cancelled". A value of `{100%}` would then have
- /// the line span precisely the element's diagonal.
- ///
- /// ```example
- /// >>> #set page(width: 140pt)
- /// $ a + cancel(x, length: #200%)
- /// - cancel(x, length: #200%) $
- /// ```
- #[default(Rel::new(Ratio::one(), Abs::pt(3.0).into()))]
- pub length: Rel<Length>,
-
- /// If the cancel line should be inverted (pointing to the top left instead
- /// of top right).
- ///
- /// ```example
- /// >>> #set page(width: 140pt)
- /// $ (a cancel((b + c), inverted: #true)) /
- /// cancel(b + c, inverted: #true) $
- /// ```
- #[default(false)]
- pub inverted: bool,
-
- /// If two opposing cancel lines should be drawn, forming a cross over the
- /// element. Overrides `inverted`.
- ///
- /// ```example
- /// >>> #set page(width: 140pt)
- /// $ cancel(Pi, cross: #true) $
- /// ```
- #[default(false)]
- pub cross: bool,
-
- /// How to rotate the cancel line. See the [line's
- /// documentation]($func/line.angle) for more details.
- ///
- /// ```example
- /// >>> #set page(width: 140pt)
- /// $ cancel(Pi, rotation: #30deg) $
- /// ```
- #[default(Angle::zero())]
- pub rotation: Angle,
-
- /// How to stroke the cancel line. See the
- /// [line's documentation]($func/line.stroke) for more details.
- ///
- /// ```example
- /// >>> #set page(width: 140pt)
- /// $ cancel(
- /// sum x,
- /// stroke: #(
- /// paint: red,
- /// thickness: 1.5pt,
- /// dash: "dashed",
- /// ),
- /// ) $
- /// ```
- #[resolve]
- #[fold]
- #[default(PartialStroke {
- // Default stroke has 0.5pt for better visuals.
- thickness: Smart::Custom(Abs::pt(0.5)),
- ..Default::default()
- })]
- pub stroke: PartialStroke,
-}
-
-impl LayoutMath for CancelElem {
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- let body = ctx.layout_fragment(&self.body())?;
- // Use the same math class as the body, in order to preserve automatic spacing around it.
- let body_class = body.class().unwrap_or(MathClass::Special);
- let mut body = body.into_frame();
-
- let styles = ctx.styles();
- let body_size = body.size();
- let span = self.span();
- let length = self.length(styles).resolve(styles);
-
- let stroke = self.stroke(styles).unwrap_or(Stroke {
- paint: TextElem::fill_in(styles),
- ..Default::default()
- });
-
- let invert = self.inverted(styles);
- let cross = self.cross(styles);
- let angle = self.rotation(styles);
-
- let invert_first_line = !cross && invert;
- let first_line = draw_cancel_line(
- length,
- stroke.clone(),
- invert_first_line,
- angle,
- body_size,
- span,
- );
-
- // The origin of our line is the very middle of the element.
- let center = body_size.to_point() / 2.0;
- body.push_frame(center, first_line);
-
- if cross {
- // Draw the second line.
- let second_line =
- draw_cancel_line(length, stroke, true, angle, body_size, span);
-
- body.push_frame(center, second_line);
- }
-
- ctx.push(FrameFragment::new(ctx, body).with_class(body_class));
-
- Ok(())
- }
-}
-
-/// Draws a cancel line.
-fn draw_cancel_line(
- length: Rel<Abs>,
- stroke: Stroke,
- invert: bool,
- angle: Angle,
- body_size: Size,
- span: Span,
-) -> Frame {
- // B
- // /|
- // diagonal / | height
- // / |
- // / |
- // O ----
- // width
- let diagonal = body_size.to_point().hypot();
- let length = length.relative_to(diagonal);
- let (width, height) = (body_size.x, body_size.y);
- let mid = body_size / 2.0;
-
- // Scale the amount needed such that the cancel line has the given 'length'
- // (reference length, or 100%, is the whole diagonal).
- // Scales from the center.
- let scale = length.to_raw() / diagonal.to_raw();
-
- // invert horizontally if 'invert' was given
- let scale_x = scale * if invert { -1.0 } else { 1.0 };
- let scale_y = scale;
- let scales = Axes::new(scale_x, scale_y);
-
- // Draw a line from bottom left to top right of the given element, where the
- // origin represents the very middle of that element, that is, a line from
- // (-width / 2, height / 2) with length components (width, -height) (sign is
- // inverted in the y-axis). After applying the scale, the line will have the
- // correct length and orientation (inverted if needed).
- let start = Axes::new(-mid.x, mid.y).zip(scales).map(|(l, s)| l * s);
- let delta = Axes::new(width, -height).zip(scales).map(|(l, s)| l * s);
-
- let mut frame = Frame::new(body_size);
- frame.push(
- start.to_point(),
- FrameItem::Shape(Geometry::Line(delta.to_point()).stroked(stroke), span),
- );
-
- // Having the middle of the line at the origin is convenient here.
- frame.transform(Transform::rotate(angle));
- frame
-}
diff --git a/library/src/math/ctx.rs b/library/src/math/ctx.rs
deleted file mode 100644
index a1dc6cf4..00000000
--- a/library/src/math/ctx.rs
+++ /dev/null
@@ -1,268 +0,0 @@
-use ttf_parser::math::MathValue;
-use typst::font::{FontStyle, FontWeight};
-use typst::model::realize;
-use unicode_segmentation::UnicodeSegmentation;
-
-use super::*;
-
-macro_rules! scaled {
- ($ctx:expr, text: $text:ident, display: $display:ident $(,)?) => {
- match $ctx.style.size {
- MathSize::Display => scaled!($ctx, $display),
- _ => scaled!($ctx, $text),
- }
- };
- ($ctx:expr, $name:ident) => {
- $ctx.constants.$name().scaled($ctx)
- };
-}
-
-macro_rules! percent {
- ($ctx:expr, $name:ident) => {
- $ctx.constants.$name() as f64 / 100.0
- };
-}
-
-/// The context for math layout.
-pub struct MathContext<'a, 'b, 'v> {
- pub vt: &'v mut Vt<'b>,
- pub regions: Regions<'static>,
- pub font: &'a Font,
- pub ttf: &'a ttf_parser::Face<'a>,
- pub table: ttf_parser::math::Table<'a>,
- pub constants: ttf_parser::math::Constants<'a>,
- pub ssty_table: Option<ttf_parser::gsub::AlternateSubstitution<'a>>,
- pub space_width: Em,
- pub fragments: Vec<MathFragment>,
- pub local: Styles,
- pub style: MathStyle,
- pub size: Abs,
- outer: StyleChain<'a>,
- style_stack: Vec<(MathStyle, Abs)>,
-}
-
-impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
- pub fn new(
- vt: &'v mut Vt<'b>,
- styles: StyleChain<'a>,
- regions: Regions,
- font: &'a Font,
- block: bool,
- ) -> Self {
- let table = font.ttf().tables().math.unwrap();
- let constants = table.constants.unwrap();
-
- let ssty_table = font
- .ttf()
- .tables()
- .gsub
- .and_then(|gsub| {
- gsub.features
- .find(ttf_parser::Tag::from_bytes(b"ssty"))
- .and_then(|feature| feature.lookup_indices.get(0))
- .and_then(|index| gsub.lookups.get(index))
- })
- .and_then(|ssty| {
- ssty.subtables.get::<ttf_parser::gsub::SubstitutionSubtable>(0)
- })
- .and_then(|ssty| match ssty {
- ttf_parser::gsub::SubstitutionSubtable::Alternate(alt_glyphs) => {
- Some(alt_glyphs)
- }
- _ => None,
- });
-
- let size = TextElem::size_in(styles);
- let ttf = font.ttf();
- let space_width = ttf
- .glyph_index(' ')
- .and_then(|id| ttf.glyph_hor_advance(id))
- .map(|advance| font.to_em(advance))
- .unwrap_or(THICK);
-
- let variant = variant(styles);
- Self {
- vt,
- regions: Regions::one(regions.base(), Axes::splat(false)),
- font,
- ttf: font.ttf(),
- table,
- constants,
- ssty_table,
- space_width,
- fragments: vec![],
- local: Styles::new(),
- style: MathStyle {
- variant: MathVariant::Serif,
- size: if block { MathSize::Display } else { MathSize::Text },
- cramped: false,
- bold: variant.weight >= FontWeight::BOLD,
- italic: match variant.style {
- FontStyle::Normal => Smart::Auto,
- FontStyle::Italic | FontStyle::Oblique => Smart::Custom(true),
- },
- },
- size,
- outer: styles,
- style_stack: vec![],
- }
- }
-
- pub fn push(&mut self, fragment: impl Into<MathFragment>) {
- self.fragments.push(fragment.into());
- }
-
- pub fn extend(&mut self, fragments: Vec<MathFragment>) {
- self.fragments.extend(fragments);
- }
-
- pub fn layout_fragment(
- &mut self,
- elem: &dyn LayoutMath,
- ) -> SourceResult<MathFragment> {
- let row = self.layout_fragments(elem)?;
- Ok(MathRow::new(row).into_fragment(self))
- }
-
- pub fn layout_fragments(
- &mut self,
- elem: &dyn LayoutMath,
- ) -> SourceResult<Vec<MathFragment>> {
- let prev = std::mem::take(&mut self.fragments);
- elem.layout_math(self)?;
- Ok(std::mem::replace(&mut self.fragments, prev))
- }
-
- pub fn layout_row(&mut self, elem: &dyn LayoutMath) -> SourceResult<MathRow> {
- let fragments = self.layout_fragments(elem)?;
- Ok(MathRow::new(fragments))
- }
-
- pub fn layout_frame(&mut self, elem: &dyn LayoutMath) -> SourceResult<Frame> {
- Ok(self.layout_fragment(elem)?.into_frame())
- }
-
- pub fn layout_content(&mut self, content: &Content) -> SourceResult<Frame> {
- Ok(content
- .layout(self.vt, self.outer.chain(&self.local), self.regions)?
- .into_frame())
- }
-
- pub fn layout_text(&mut self, elem: &TextElem) -> SourceResult<MathFragment> {
- let text = elem.text();
- let span = elem.span();
- let mut chars = text.chars();
- let fragment = if let Some(mut glyph) = chars
- .next()
- .filter(|_| chars.next().is_none())
- .map(|c| self.style.styled_char(c))
- .and_then(|c| GlyphFragment::try_new(self, c, span))
- {
- // A single letter that is available in the math font.
- match self.style.size {
- MathSize::Display => {
- if glyph.class == Some(MathClass::Large) {
- let height = scaled!(self, display_operator_min_height);
- glyph.stretch_vertical(self, height, Abs::zero()).into()
- } else {
- glyph.into()
- }
- }
- MathSize::Script => {
- glyph.make_scriptsize(self);
- glyph.into()
- }
- MathSize::ScriptScript => {
- glyph.make_scriptscriptsize(self);
- glyph.into()
- }
- _ => glyph.into(),
- }
- } else if text.chars().all(|c| c.is_ascii_digit()) {
- // Numbers aren't that difficult.
- let mut fragments = vec![];
- for c in text.chars() {
- let c = self.style.styled_char(c);
- fragments.push(GlyphFragment::new(self, c, span).into());
- }
- let frame = MathRow::new(fragments).into_frame(self);
- FrameFragment::new(self, frame).into()
- } else {
- // Anything else is handled by Typst's standard text layout.
- let spaced = text.graphemes(true).nth(1).is_some();
- let mut style = self.style;
- if self.style.italic == Smart::Auto {
- style = style.with_italic(false);
- }
- let text: EcoString = text.chars().map(|c| style.styled_char(c)).collect();
- let frame = self.layout_content(&TextElem::packed(text).spanned(span))?;
- FrameFragment::new(self, frame)
- .with_class(MathClass::Alphabetic)
- .with_spaced(spaced)
- .into()
- };
- Ok(fragment)
- }
-
- pub fn styles(&self) -> StyleChain {
- self.outer.chain(&self.local)
- }
-
- pub fn realize(&mut self, content: &Content) -> SourceResult<Option<Content>> {
- realize(self.vt, content, self.outer.chain(&self.local))
- }
-
- pub fn style(&mut self, style: MathStyle) {
- self.style_stack.push((self.style, self.size));
- let base_size = TextElem::size_in(self.styles()) / self.style.size.factor(self);
- self.size = base_size * style.size.factor(self);
- self.local.set(TextElem::set_size(TextSize(self.size.into())));
- self.local
- .set(TextElem::set_style(if style.italic == Smart::Custom(true) {
- FontStyle::Italic
- } else {
- FontStyle::Normal
- }));
- self.local.set(TextElem::set_weight(if style.bold {
- FontWeight::BOLD
- } else {
- FontWeight::REGULAR
- }));
- self.style = style;
- }
-
- pub fn unstyle(&mut self) {
- (self.style, self.size) = self.style_stack.pop().unwrap();
- self.local.unset();
- self.local.unset();
- self.local.unset();
- }
-}
-
-pub(super) trait Scaled {
- fn scaled(self, ctx: &MathContext) -> Abs;
-}
-
-impl Scaled for i16 {
- fn scaled(self, ctx: &MathContext) -> Abs {
- ctx.font.to_em(self).scaled(ctx)
- }
-}
-
-impl Scaled for u16 {
- fn scaled(self, ctx: &MathContext) -> Abs {
- ctx.font.to_em(self).scaled(ctx)
- }
-}
-
-impl Scaled for Em {
- fn scaled(self, ctx: &MathContext) -> Abs {
- self.at(ctx.size)
- }
-}
-
-impl Scaled for MathValue<'_> {
- fn scaled(self, ctx: &MathContext) -> Abs {
- self.value.scaled(ctx)
- }
-}
diff --git a/library/src/math/delimited.rs b/library/src/math/delimited.rs
deleted file mode 100644
index 99cd6c33..00000000
--- a/library/src/math/delimited.rs
+++ /dev/null
@@ -1,200 +0,0 @@
-use super::*;
-
-/// How much less high scaled delimiters can be than what they wrap.
-pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1);
-
-/// Scales delimiters.
-///
-/// While matched delimiters scale by default, this can be used to scale
-/// unmatched delimiters and to control the delimiter scaling more precisely.
-///
-/// ## Example { #example }
-/// ```example
-/// $ lr(]a, b/2]) $
-/// $ lr(]sum_(x=1)^n] x, size: #50%) $
-/// ```
-///
-/// Display: Left/Right
-/// Category: math
-#[element(LayoutMath)]
-pub struct LrElem {
- /// The size of the brackets, relative to the height of the wrapped content.
- pub size: Smart<Rel<Length>>,
-
- /// The delimited content, including the delimiters.
- #[required]
- #[parse(
- let mut body = Content::empty();
- for (i, arg) in args.all::<Content>()?.into_iter().enumerate() {
- if i > 0 {
- body += TextElem::packed(',');
- }
- body += arg;
- }
- body
- )]
- pub body: Content,
-}
-
-impl LayoutMath for LrElem {
- #[tracing::instrument(skip(ctx))]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- let mut body = self.body();
- if let Some(elem) = body.to::<LrElem>() {
- if elem.size(ctx.styles()).is_auto() {
- body = elem.body();
- }
- }
-
- let mut fragments = ctx.layout_fragments(&body)?;
- let axis = scaled!(ctx, axis_height);
- let max_extent = fragments
- .iter()
- .map(|fragment| (fragment.ascent() - axis).max(fragment.descent() + axis))
- .max()
- .unwrap_or_default();
-
- let height = self
- .size(ctx.styles())
- .unwrap_or(Rel::one())
- .resolve(ctx.styles())
- .relative_to(2.0 * max_extent);
-
- match fragments.as_mut_slice() {
- [one] => scale(ctx, one, height, None),
- [first, .., last] => {
- scale(ctx, first, height, Some(MathClass::Opening));
- scale(ctx, last, height, Some(MathClass::Closing));
- }
- _ => {}
- }
-
- ctx.extend(fragments);
-
- Ok(())
- }
-}
-
-/// Scale a math fragment to a height.
-fn scale(
- ctx: &mut MathContext,
- fragment: &mut MathFragment,
- height: Abs,
- apply: Option<MathClass>,
-) {
- if matches!(
- fragment.class(),
- Some(MathClass::Opening | MathClass::Closing | MathClass::Fence)
- ) {
- let glyph = match fragment {
- MathFragment::Glyph(glyph) => glyph.clone(),
- MathFragment::Variant(variant) => {
- GlyphFragment::new(ctx, variant.c, variant.span)
- }
- _ => return,
- };
-
- let short_fall = DELIM_SHORT_FALL.scaled(ctx);
- *fragment =
- MathFragment::Variant(glyph.stretch_vertical(ctx, height, short_fall));
-
- if let Some(class) = apply {
- fragment.set_class(class);
- }
- }
-}
-
-/// Floors an expression.
-///
-/// ## Example { #example }
-/// ```example
-/// $ floor(x/2) $
-/// ```
-///
-/// Display: Floor
-/// Category: math
-#[func]
-pub fn floor(
- /// The expression to floor.
- body: Content,
-) -> Content {
- delimited(body, '⌊', '⌋')
-}
-
-/// Ceils an expression.
-///
-/// ## Example { #example }
-/// ```example
-/// $ ceil(x/2) $
-/// ```
-///
-/// Display: Ceil
-/// Category: math
-#[func]
-pub fn ceil(
- /// The expression to ceil.
- body: Content,
-) -> Content {
- delimited(body, '⌈', '⌉')
-}
-
-/// Rounds an expression.
-///
-/// ## Example { #example }
-/// ```example
-/// $ round(x/2) $
-/// ```
-///
-/// Display: Round
-/// Category: math
-#[func]
-pub fn round(
- /// The expression to round.
- body: Content,
-) -> Content {
- delimited(body, '⌊', '⌉')
-}
-
-/// Takes the absolute value of an expression.
-///
-/// ## Example { #example }
-/// ```example
-/// $ abs(x/2) $
-/// ```
-///
-///
-/// Display: Abs
-/// Category: math
-#[func]
-pub fn abs(
- /// The expression to take the absolute value of.
- body: Content,
-) -> Content {
- delimited(body, '|', '|')
-}
-
-/// Takes the norm of an expression.
-///
-/// ## Example { #example }
-/// ```example
-/// $ norm(x/2) $
-/// ```
-///
-/// Display: Norm
-/// Category: math
-#[func]
-pub fn norm(
- /// The expression to take the norm of.
- body: Content,
-) -> Content {
- delimited(body, '‖', '‖')
-}
-
-fn delimited(body: Content, left: char, right: char) -> Content {
- LrElem::new(Content::sequence([
- TextElem::packed(left),
- body,
- TextElem::packed(right),
- ]))
- .pack()
-}
diff --git a/library/src/math/frac.rs b/library/src/math/frac.rs
deleted file mode 100644
index 0e1f78cc..00000000
--- a/library/src/math/frac.rs
+++ /dev/null
@@ -1,148 +0,0 @@
-use super::*;
-
-const FRAC_AROUND: Em = Em::new(0.1);
-
-/// A mathematical fraction.
-///
-/// ## Example { #example }
-/// ```example
-/// $ 1/2 < (x+1)/2 $
-/// $ ((x+1)) / 2 = frac(a, b) $
-/// ```
-///
-/// ## Syntax { #syntax }
-/// This function also has dedicated syntax: Use a slash to turn neighbouring
-/// expressions into a fraction. Multiple atoms can be grouped into a single
-/// expression using round grouping parenthesis. Such parentheses are removed
-/// from the output, but you can nest multiple to force them.
-///
-/// Display: Fraction
-/// Category: math
-#[element(LayoutMath)]
-pub struct FracElem {
- /// The fraction's numerator.
- #[required]
- pub num: Content,
-
- /// The fraction's denominator.
- #[required]
- pub denom: Content,
-}
-
-impl LayoutMath for FracElem {
- #[tracing::instrument(skip(ctx))]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- layout(ctx, &self.num(), &self.denom(), false, self.span())
- }
-}
-
-/// A binomial expression.
-///
-/// ## Example { #example }
-/// ```example
-/// $ binom(n, k) $
-/// ```
-///
-/// Display: Binomial
-/// Category: math
-#[element(LayoutMath)]
-pub struct BinomElem {
- /// The binomial's upper index.
- #[required]
- pub upper: Content,
-
- /// The binomial's lower index.
- #[required]
- pub lower: Content,
-}
-
-impl LayoutMath for BinomElem {
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- layout(ctx, &self.upper(), &self.lower(), true, self.span())
- }
-}
-
-/// Layout a fraction or binomial.
-fn layout(
- ctx: &mut MathContext,
- num: &Content,
- denom: &Content,
- binom: bool,
- span: Span,
-) -> SourceResult<()> {
- let short_fall = DELIM_SHORT_FALL.scaled(ctx);
- let axis = scaled!(ctx, axis_height);
- let thickness = scaled!(ctx, fraction_rule_thickness);
- let shift_up = scaled!(
- ctx,
- text: fraction_numerator_shift_up,
- display: fraction_numerator_display_style_shift_up,
- );
- let shift_down = scaled!(
- ctx,
- text: fraction_denominator_shift_down,
- display: fraction_denominator_display_style_shift_down,
- );
- let num_min = scaled!(
- ctx,
- text: fraction_numerator_gap_min,
- display: fraction_num_display_style_gap_min,
- );
- let denom_min = scaled!(
- ctx,
- text: fraction_denominator_gap_min,
- display: fraction_denom_display_style_gap_min,
- );
-
- ctx.style(ctx.style.for_numerator());
- let num = ctx.layout_frame(num)?;
- ctx.unstyle();
-
- ctx.style(ctx.style.for_denominator());
- let denom = ctx.layout_frame(denom)?;
- ctx.unstyle();
-
- let around = FRAC_AROUND.scaled(ctx);
- let num_gap = (shift_up - axis - num.descent()).max(num_min + thickness / 2.0);
- let denom_gap = (shift_down + axis - denom.ascent()).max(denom_min + thickness / 2.0);
-
- let line_width = num.width().max(denom.width());
- let width = line_width + 2.0 * around;
- let height = num.height() + num_gap + thickness + denom_gap + denom.height();
- let size = Size::new(width, height);
- let num_pos = Point::with_x((width - num.width()) / 2.0);
- let line_pos =
- Point::new((width - line_width) / 2.0, num.height() + num_gap + thickness / 2.0);
- let denom_pos = Point::new((width - denom.width()) / 2.0, height - denom.height());
- let baseline = line_pos.y + axis;
-
- let mut frame = Frame::new(size);
- frame.set_baseline(baseline);
- frame.push_frame(num_pos, num);
- frame.push_frame(denom_pos, denom);
-
- if binom {
- ctx.push(
- GlyphFragment::new(ctx, '(', span).stretch_vertical(ctx, height, short_fall),
- );
- ctx.push(FrameFragment::new(ctx, frame));
- ctx.push(
- GlyphFragment::new(ctx, ')', span).stretch_vertical(ctx, height, short_fall),
- );
- } else {
- frame.push(
- line_pos,
- FrameItem::Shape(
- Geometry::Line(Point::with_x(line_width)).stroked(Stroke {
- paint: TextElem::fill_in(ctx.styles()),
- thickness,
- ..Stroke::default()
- }),
- span,
- ),
- );
- ctx.push(FrameFragment::new(ctx, frame));
- }
-
- Ok(())
-}
diff --git a/library/src/math/fragment.rs b/library/src/math/fragment.rs
deleted file mode 100644
index 139ce07b..00000000
--- a/library/src/math/fragment.rs
+++ /dev/null
@@ -1,414 +0,0 @@
-use super::*;
-use ttf_parser::gsub::AlternateSet;
-
-#[derive(Debug, Clone)]
-pub enum MathFragment {
- Glyph(GlyphFragment),
- Variant(VariantFragment),
- Frame(FrameFragment),
- Spacing(Abs),
- Space(Abs),
- Linebreak,
- Align,
-}
-
-impl MathFragment {
- pub fn size(&self) -> Size {
- Size::new(self.width(), self.height())
- }
-
- pub fn width(&self) -> Abs {
- match self {
- Self::Glyph(glyph) => glyph.width,
- Self::Variant(variant) => variant.frame.width(),
- Self::Frame(fragment) => fragment.frame.width(),
- Self::Spacing(amount) => *amount,
- Self::Space(amount) => *amount,
- _ => Abs::zero(),
- }
- }
-
- pub fn height(&self) -> Abs {
- match self {
- Self::Glyph(glyph) => glyph.height(),
- Self::Variant(variant) => variant.frame.height(),
- Self::Frame(fragment) => fragment.frame.height(),
- _ => Abs::zero(),
- }
- }
-
- pub fn ascent(&self) -> Abs {
- match self {
- Self::Glyph(glyph) => glyph.ascent,
- Self::Variant(variant) => variant.frame.ascent(),
- Self::Frame(fragment) => fragment.frame.baseline(),
- _ => Abs::zero(),
- }
- }
-
- pub fn descent(&self) -> Abs {
- match self {
- Self::Glyph(glyph) => glyph.descent,
- Self::Variant(variant) => variant.frame.descent(),
- Self::Frame(fragment) => fragment.frame.descent(),
- _ => Abs::zero(),
- }
- }
-
- pub fn class(&self) -> Option<MathClass> {
- match self {
- Self::Glyph(glyph) => glyph.class,
- Self::Variant(variant) => variant.class,
- Self::Frame(fragment) => Some(fragment.class),
- _ => None,
- }
- }
-
- pub fn style(&self) -> Option<MathStyle> {
- match self {
- Self::Glyph(glyph) => Some(glyph.style),
- Self::Variant(variant) => Some(variant.style),
- Self::Frame(fragment) => Some(fragment.style),
- _ => None,
- }
- }
-
- pub fn font_size(&self) -> Option<Abs> {
- match self {
- Self::Glyph(glyph) => Some(glyph.font_size),
- Self::Variant(variant) => Some(variant.font_size),
- Self::Frame(fragment) => Some(fragment.font_size),
- _ => None,
- }
- }
-
- pub fn set_class(&mut self, class: MathClass) {
- match self {
- Self::Glyph(glyph) => glyph.class = Some(class),
- Self::Variant(variant) => variant.class = Some(class),
- Self::Frame(fragment) => fragment.class = class,
- _ => {}
- }
- }
-
- pub fn set_limits(&mut self, limits: Limits) {
- match self {
- Self::Glyph(glyph) => glyph.limits = limits,
- Self::Variant(variant) => variant.limits = limits,
- Self::Frame(fragment) => fragment.limits = limits,
- _ => {}
- }
- }
-
- pub fn is_spaced(&self) -> bool {
- match self {
- MathFragment::Frame(frame) => frame.spaced,
- _ => self.class() == Some(MathClass::Fence),
- }
- }
-
- pub fn italics_correction(&self) -> Abs {
- match self {
- Self::Glyph(glyph) => glyph.italics_correction,
- Self::Variant(variant) => variant.italics_correction,
- _ => Abs::zero(),
- }
- }
-
- pub fn into_frame(self) -> Frame {
- match self {
- Self::Glyph(glyph) => glyph.into_frame(),
- Self::Variant(variant) => variant.frame,
- Self::Frame(fragment) => fragment.frame,
- _ => Frame::new(self.size()),
- }
- }
-
- pub fn limits(&self) -> Limits {
- match self {
- MathFragment::Glyph(glyph) => glyph.limits,
- MathFragment::Variant(variant) => variant.limits,
- MathFragment::Frame(fragment) => fragment.limits,
- _ => Limits::Never,
- }
- }
-}
-
-impl From<GlyphFragment> for MathFragment {
- fn from(glyph: GlyphFragment) -> Self {
- Self::Glyph(glyph)
- }
-}
-
-impl From<VariantFragment> for MathFragment {
- fn from(variant: VariantFragment) -> Self {
- Self::Variant(variant)
- }
-}
-
-impl From<FrameFragment> for MathFragment {
- fn from(fragment: FrameFragment) -> Self {
- Self::Frame(fragment)
- }
-}
-
-#[derive(Clone)]
-pub struct GlyphFragment {
- pub id: GlyphId,
- pub c: char,
- pub font: Font,
- pub lang: Lang,
- pub fill: Paint,
- pub width: Abs,
- pub ascent: Abs,
- pub descent: Abs,
- pub italics_correction: Abs,
- pub style: MathStyle,
- pub font_size: Abs,
- pub class: Option<MathClass>,
- pub span: Span,
- pub meta: Vec<Meta>,
- pub limits: Limits,
-}
-
-impl GlyphFragment {
- pub fn new(ctx: &MathContext, c: char, span: Span) -> Self {
- let id = ctx.ttf.glyph_index(c).unwrap_or_default();
- Self::with_id(ctx, c, id, span)
- }
-
- pub fn try_new(ctx: &MathContext, c: char, span: Span) -> Option<Self> {
- let c = ctx.style.styled_char(c);
- let id = ctx.ttf.glyph_index(c)?;
- Some(Self::with_id(ctx, c, id, span))
- }
-
- pub fn with_id(ctx: &MathContext, c: char, id: GlyphId, span: Span) -> Self {
- let class = match c {
- ':' => Some(MathClass::Relation),
- _ => unicode_math_class::class(c),
- };
- let mut fragment = Self {
- id,
- c,
- font: ctx.font.clone(),
- lang: TextElem::lang_in(ctx.styles()),
- fill: TextElem::fill_in(ctx.styles()),
- style: ctx.style,
- font_size: ctx.size,
- width: Abs::zero(),
- ascent: Abs::zero(),
- descent: Abs::zero(),
- limits: Limits::for_char(c),
- italics_correction: Abs::zero(),
- class,
- span,
- meta: MetaElem::data_in(ctx.styles()),
- };
- fragment.set_id(ctx, id);
- fragment
- }
-
- /// Sets element id and boxes in appropriate way without changing other
- /// styles. This is used to replace the glyph with a stretch variant.
- pub fn set_id(&mut self, ctx: &MathContext, id: GlyphId) {
- let advance = ctx.ttf.glyph_hor_advance(id).unwrap_or_default();
- let italics = italics_correction(ctx, id).unwrap_or_default();
- let bbox = ctx.ttf.glyph_bounding_box(id).unwrap_or(Rect {
- x_min: 0,
- y_min: 0,
- x_max: 0,
- y_max: 0,
- });
-
- let mut width = advance.scaled(ctx);
- if !is_extended_shape(ctx, id) {
- width += italics;
- }
-
- self.id = id;
- self.width = width;
- self.ascent = bbox.y_max.scaled(ctx);
- self.descent = -bbox.y_min.scaled(ctx);
- self.italics_correction = italics;
- }
-
- pub fn height(&self) -> Abs {
- self.ascent + self.descent
- }
-
- pub fn into_variant(self) -> VariantFragment {
- VariantFragment {
- c: self.c,
- id: Some(self.id),
- style: self.style,
- font_size: self.font_size,
- italics_correction: self.italics_correction,
- class: self.class,
- span: self.span,
- limits: self.limits,
- frame: self.into_frame(),
- }
- }
-
- pub fn into_frame(self) -> Frame {
- let item = TextItem {
- font: self.font.clone(),
- size: self.font_size,
- fill: self.fill,
- lang: self.lang,
- text: self.c.into(),
- glyphs: vec![Glyph {
- id: self.id.0,
- x_advance: Em::from_length(self.width, self.font_size),
- x_offset: Em::zero(),
- range: 0..self.c.len_utf8() as u16,
- span: (self.span, 0),
- }],
- };
- let size = Size::new(self.width, self.ascent + self.descent);
- let mut frame = Frame::new(size);
- frame.set_baseline(self.ascent);
- frame.push(Point::with_y(self.ascent), FrameItem::Text(item));
- frame.meta_iter(self.meta);
- frame
- }
-
- pub fn make_scriptsize(&mut self, ctx: &MathContext) {
- let alt_id =
- script_alternatives(ctx, self.id).and_then(|alts| alts.alternates.get(0));
-
- if let Some(alt_id) = alt_id {
- self.set_id(ctx, alt_id);
- }
- }
-
- pub fn make_scriptscriptsize(&mut self, ctx: &MathContext) {
- let alts = script_alternatives(ctx, self.id);
- let alt_id = alts
- .and_then(|alts| alts.alternates.get(1).or_else(|| alts.alternates.get(0)));
-
- if let Some(alt_id) = alt_id {
- self.set_id(ctx, alt_id);
- }
- }
-}
-
-impl Debug for GlyphFragment {
- fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
- write!(f, "GlyphFragment({:?})", self.c)
- }
-}
-
-#[derive(Clone)]
-pub struct VariantFragment {
- pub c: char,
- pub id: Option<GlyphId>,
- pub italics_correction: Abs,
- pub frame: Frame,
- pub style: MathStyle,
- pub font_size: Abs,
- pub class: Option<MathClass>,
- pub span: Span,
- pub limits: Limits,
-}
-
-impl Debug for VariantFragment {
- fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
- write!(f, "VariantFragment({:?})", self.c)
- }
-}
-
-#[derive(Debug, Clone)]
-pub struct FrameFragment {
- pub frame: Frame,
- pub style: MathStyle,
- pub font_size: Abs,
- pub class: MathClass,
- pub limits: Limits,
- pub spaced: bool,
- pub base_ascent: Abs,
-}
-
-impl FrameFragment {
- pub fn new(ctx: &MathContext, mut frame: Frame) -> Self {
- let base_ascent = frame.ascent();
- frame.meta(ctx.styles(), false);
- Self {
- frame,
- font_size: ctx.size,
- style: ctx.style,
- class: MathClass::Normal,
- limits: Limits::Never,
- spaced: false,
- base_ascent,
- }
- }
-
- pub fn with_class(self, class: MathClass) -> Self {
- Self { class, ..self }
- }
-
- pub fn with_limits(self, limits: Limits) -> Self {
- Self { limits, ..self }
- }
-
- pub fn with_spaced(self, spaced: bool) -> Self {
- Self { spaced, ..self }
- }
-
- pub fn with_base_ascent(self, base_ascent: Abs) -> Self {
- Self { base_ascent, ..self }
- }
-}
-
-/// Look up the italics correction for a glyph.
-fn italics_correction(ctx: &MathContext, id: GlyphId) -> Option<Abs> {
- Some(ctx.table.glyph_info?.italic_corrections?.get(id)?.scaled(ctx))
-}
-
-/// Look up the script/scriptscript alternates for a glyph
-fn script_alternatives<'a>(
- ctx: &MathContext<'a, '_, '_>,
- id: GlyphId,
-) -> Option<AlternateSet<'a>> {
- ctx.ssty_table.and_then(|ssty| {
- ssty.coverage.get(id).and_then(|index| ssty.alternate_sets.get(index))
- })
-}
-
-/// Look up the italics correction for a glyph.
-fn is_extended_shape(ctx: &MathContext, id: GlyphId) -> bool {
- ctx.table
- .glyph_info
- .and_then(|info| info.extended_shapes)
- .and_then(|info| info.get(id))
- .is_some()
-}
-
-/// Look up a kerning value at a specific corner and height.
-///
-/// This can be integrated once we've found a font that actually provides this
-/// data.
-#[allow(unused)]
-fn kern_at_height(
- ctx: &MathContext,
- id: GlyphId,
- corner: Corner,
- height: Abs,
-) -> Option<Abs> {
- let kerns = ctx.table.glyph_info?.kern_infos?.get(id)?;
- let kern = match corner {
- Corner::TopLeft => kerns.top_left,
- Corner::TopRight => kerns.top_right,
- Corner::BottomRight => kerns.bottom_right,
- Corner::BottomLeft => kerns.bottom_left,
- }?;
-
- let mut i = 0;
- while i < kern.count() && height > kern.height(i)?.scaled(ctx) {
- i += 1;
- }
-
- Some(kern.kern(i)?.scaled(ctx))
-}
diff --git a/library/src/math/matrix.rs b/library/src/math/matrix.rs
deleted file mode 100644
index aaccc332..00000000
--- a/library/src/math/matrix.rs
+++ /dev/null
@@ -1,313 +0,0 @@
-use super::*;
-
-const ROW_GAP: Em = Em::new(0.5);
-const COL_GAP: Em = Em::new(0.5);
-const VERTICAL_PADDING: Ratio = Ratio::new(0.1);
-
-/// A column vector.
-///
-/// Content in the vector's elements can be aligned with the `&` symbol.
-///
-/// ## Example { #example }
-/// ```example
-/// $ vec(a, b, c) dot vec(1, 2, 3)
-/// = a + 2b + 3c $
-/// ```
-///
-/// Display: Vector
-/// Category: math
-#[element(LayoutMath)]
-pub struct VecElem {
- /// The delimiter to use.
- ///
- /// ```example
- /// #set math.vec(delim: "[")
- /// $ vec(1, 2) $
- /// ```
- #[default(Some(Delimiter::Paren))]
- pub delim: Option<Delimiter>,
-
- /// The elements of the vector.
- #[variadic]
- pub children: Vec<Content>,
-}
-
-impl LayoutMath for VecElem {
- #[tracing::instrument(skip(ctx))]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- let delim = self.delim(ctx.styles());
- let frame = layout_vec_body(ctx, &self.children(), Align::Center)?;
- layout_delimiters(
- ctx,
- frame,
- delim.map(Delimiter::open),
- delim.map(Delimiter::close),
- self.span(),
- )
- }
-}
-
-/// A matrix.
-///
-/// The elements of a row should be separated by commas, while the rows
-/// themselves should be separated by semicolons. The semicolon syntax merges
-/// preceding arguments separated by commas into an array. You can also use this
-/// special syntax of math function calls to define custom functions that take
-/// 2D data.
-///
-/// Content in cells that are in the same row can be aligned with the `&` symbol.
-///
-/// ## Example { #example }
-/// ```example
-/// $ mat(
-/// 1, 2, ..., 10;
-/// 2, 2, ..., 10;
-/// dots.v, dots.v, dots.down, dots.v;
-/// 10, 10, ..., 10;
-/// ) $
-/// ```
-///
-/// Display: Matrix
-/// Category: math
-#[element(LayoutMath)]
-pub struct MatElem {
- /// The delimiter to use.
- ///
- /// ```example
- /// #set math.mat(delim: "[")
- /// $ mat(1, 2; 3, 4) $
- /// ```
- #[default(Some(Delimiter::Paren))]
- pub delim: Option<Delimiter>,
-
- /// An array of arrays with the rows of the matrix.
- ///
- /// ```example
- /// #let data = ((1, 2, 3), (4, 5, 6))
- /// #let matrix = math.mat(..data)
- /// $ v := matrix $
- /// ```
- #[variadic]
- #[parse(
- let mut rows = vec![];
- let mut width = 0;
-
- let values = args.all::<Spanned<Value>>()?;
- if values.iter().any(|spanned| matches!(spanned.v, Value::Array(_))) {
- for Spanned { v, span } in values {
- let array = v.cast::<Array>().at(span)?;
- let row: Vec<_> = array.into_iter().map(Value::display).collect();
- width = width.max(row.len());
- rows.push(row);
- }
- } else {
- rows = vec![values.into_iter().map(|spanned| spanned.v.display()).collect()];
- }
-
- for row in &mut rows {
- if row.len() < width {
- row.resize(width, Content::empty());
- }
- }
-
- rows
- )]
- pub rows: Vec<Vec<Content>>,
-}
-
-impl LayoutMath for MatElem {
- #[tracing::instrument(skip(ctx))]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- let delim = self.delim(ctx.styles());
- let frame = layout_mat_body(ctx, &self.rows())?;
- layout_delimiters(
- ctx,
- frame,
- delim.map(Delimiter::open),
- delim.map(Delimiter::close),
- self.span(),
- )
- }
-}
-
-/// A case distinction.
-///
-/// Content across different branches can be aligned with the `&` symbol.
-///
-/// ## Example { #example }
-/// ```example
-/// $ f(x, y) := cases(
-/// 1 "if" (x dot y)/2 <= 0,
-/// 2 "if" x "is even",
-/// 3 "if" x in NN,
-/// 4 "else",
-/// ) $
-/// ```
-///
-/// Display: Cases
-/// Category: math
-#[element(LayoutMath)]
-pub struct CasesElem {
- /// The delimiter to use.
- ///
- /// ```example
- /// #set math.cases(delim: "[")
- /// $ x = cases(1, 2) $
- /// ```
- #[default(Delimiter::Brace)]
- pub delim: Delimiter,
-
- /// The branches of the case distinction.
- #[variadic]
- pub children: Vec<Content>,
-}
-
-impl LayoutMath for CasesElem {
- #[tracing::instrument(skip(ctx))]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- let delim = self.delim(ctx.styles());
- let frame = layout_vec_body(ctx, &self.children(), Align::Left)?;
- layout_delimiters(ctx, frame, Some(delim.open()), None, self.span())
- }
-}
-
-/// A vector / matrix delimiter.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
-pub enum Delimiter {
- /// Delimit with parentheses.
- #[string("(")]
- Paren,
- /// Delimit with brackets.
- #[string("[")]
- Bracket,
- /// Delimit with curly braces.
- #[string("{")]
- Brace,
- /// Delimit with vertical bars.
- #[string("|")]
- Bar,
- /// Delimit with double vertical bars.
- #[string("||")]
- DoubleBar,
-}
-
-impl Delimiter {
- /// The delimiter's opening character.
- fn open(self) -> char {
- match self {
- Self::Paren => '(',
- Self::Bracket => '[',
- Self::Brace => '{',
- Self::Bar => '|',
- Self::DoubleBar => '‖',
- }
- }
-
- /// The delimiter's closing character.
- fn close(self) -> char {
- match self {
- Self::Paren => ')',
- Self::Bracket => ']',
- Self::Brace => '}',
- Self::Bar => '|',
- Self::DoubleBar => '‖',
- }
- }
-}
-
-/// Layout the inner contents of a vector.
-fn layout_vec_body(
- ctx: &mut MathContext,
- column: &[Content],
- align: Align,
-) -> SourceResult<Frame> {
- let gap = ROW_GAP.scaled(ctx);
- ctx.style(ctx.style.for_denominator());
- let mut flat = vec![];
- for child in column {
- flat.push(ctx.layout_row(child)?);
- }
- ctx.unstyle();
- Ok(stack(ctx, flat, align, gap, 0))
-}
-
-/// Layout the inner contents of a matrix.
-fn layout_mat_body(ctx: &mut MathContext, rows: &[Vec<Content>]) -> SourceResult<Frame> {
- let row_gap = ROW_GAP.scaled(ctx);
- let col_gap = COL_GAP.scaled(ctx);
-
- let ncols = rows.first().map_or(0, |row| row.len());
- let nrows = rows.len();
- if ncols == 0 || nrows == 0 {
- return Ok(Frame::new(Size::zero()));
- }
-
- let mut heights = vec![(Abs::zero(), Abs::zero()); nrows];
-
- ctx.style(ctx.style.for_denominator());
- let mut cols = vec![vec![]; ncols];
- for (row, (ascent, descent)) in rows.iter().zip(&mut heights) {
- for (cell, col) in row.iter().zip(&mut cols) {
- let cell = ctx.layout_row(cell)?;
- ascent.set_max(cell.ascent());
- descent.set_max(cell.descent());
- col.push(cell);
- }
- }
- ctx.unstyle();
-
- let mut frame = Frame::new(Size::new(
- Abs::zero(),
- heights.iter().map(|&(a, b)| a + b).sum::<Abs>() + row_gap * (nrows - 1) as f64,
- ));
- let mut x = Abs::zero();
- for col in cols {
- let AlignmentResult { points, width: rcol } = alignments(&col);
- let mut y = Abs::zero();
- for (cell, &(ascent, descent)) in col.into_iter().zip(&heights) {
- let cell = cell.into_aligned_frame(ctx, &points, Align::Center);
- let pos = Point::new(
- if points.is_empty() { x + (rcol - cell.width()) / 2.0 } else { x },
- y + ascent - cell.ascent(),
- );
- frame.push_frame(pos, cell);
- y += ascent + descent + row_gap;
- }
- x += rcol + col_gap;
- }
- frame.size_mut().x = x - col_gap;
-
- Ok(frame)
-}
-
-/// Layout the outer wrapper around a vector's or matrices' body.
-fn layout_delimiters(
- ctx: &mut MathContext,
- mut frame: Frame,
- left: Option<char>,
- right: Option<char>,
- span: Span,
-) -> SourceResult<()> {
- let axis = scaled!(ctx, axis_height);
- let short_fall = DELIM_SHORT_FALL.scaled(ctx);
- let height = frame.height();
- let target = height + VERTICAL_PADDING.of(height);
- frame.set_baseline(height / 2.0 + axis);
-
- if let Some(left) = left {
- ctx.push(
- GlyphFragment::new(ctx, left, span).stretch_vertical(ctx, target, short_fall),
- );
- }
-
- ctx.push(FrameFragment::new(ctx, frame));
-
- if let Some(right) = right {
- ctx.push(
- GlyphFragment::new(ctx, right, span)
- .stretch_vertical(ctx, target, short_fall),
- );
- }
-
- Ok(())
-}
diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs
deleted file mode 100644
index 0429265f..00000000
--- a/library/src/math/mod.rs
+++ /dev/null
@@ -1,480 +0,0 @@
-//! Mathematical formulas.
-
-#[macro_use]
-mod ctx;
-mod accent;
-mod align;
-mod attach;
-mod cancel;
-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::cancel::*;
-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::eval::{Module, Scope};
-use typst::font::{Font, FontWeight};
-use typst::model::Guard;
-use typst::util::option_eq;
-use unicode_math_class::MathClass;
-
-use self::ctx::*;
-use self::fragment::*;
-use self::row::*;
-use self::spacing::*;
-use crate::layout::{HElem, ParElem, Spacing};
-use crate::meta::Supplement;
-use crate::meta::{
- Count, Counter, CounterUpdate, LocalName, Numbering, Outlinable, Refable,
-};
-use crate::prelude::*;
-use crate::text::{
- families, variant, FontFamily, FontList, LinebreakElem, SpaceElem, TextElem, TextSize,
-};
-
-/// Create a module with all math definitions.
-pub fn module() -> Module {
- let mut math = Scope::deduplicating();
- math.define("equation", EquationElem::func());
- math.define("text", TextElem::func());
-
- // Grouping.
- math.define("lr", LrElem::func());
- math.define("abs", abs_func());
- math.define("norm", norm_func());
- math.define("floor", floor_func());
- math.define("ceil", ceil_func());
- math.define("round", round_func());
-
- // Attachments and accents.
- math.define("attach", AttachElem::func());
- math.define("scripts", ScriptsElem::func());
- math.define("limits", LimitsElem::func());
- math.define("accent", AccentElem::func());
- math.define("underline", UnderlineElem::func());
- math.define("overline", OverlineElem::func());
- math.define("underbrace", UnderbraceElem::func());
- math.define("overbrace", OverbraceElem::func());
- math.define("underbracket", UnderbracketElem::func());
- math.define("overbracket", OverbracketElem::func());
- math.define("cancel", CancelElem::func());
-
- // Fractions and matrix-likes.
- math.define("frac", FracElem::func());
- math.define("binom", BinomElem::func());
- math.define("vec", VecElem::func());
- math.define("mat", MatElem::func());
- math.define("cases", CasesElem::func());
-
- // Roots.
- math.define("sqrt", sqrt_func());
- math.define("root", RootElem::func());
-
- // Styles.
- math.define("upright", upright_func());
- math.define("bold", bold_func());
- math.define("italic", italic_func());
- math.define("serif", serif_func());
- math.define("sans", sans_func());
- math.define("cal", cal_func());
- math.define("frak", frak_func());
- math.define("mono", mono_func());
- math.define("bb", bb_func());
-
- math.define("display", display_func());
- math.define("inline", inline_func());
- math.define("script", script_func());
- math.define("sscript", sscript_func());
-
- // Text operators.
- math.define("op", OpElem::func());
- 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)
-}
-
-/// A mathematical equation.
-///
-/// Can be displayed inline with text or as a separate block.
-///
-/// ## Example { #example }
-/// ```example
-/// #set text(font: "New Computer Modern")
-///
-/// 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 { #syntax }
-/// This function also has dedicated syntax: Write mathematical markup within
-/// dollar signs to create an equation. Starting and ending the equation 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]($category/math).
-///
-/// Display: Equation
-/// Category: math
-#[element(
- Locatable, Synthesize, Show, Finalize, Layout, LayoutMath, Count, LocalName, Refable,
- Outlinable
-)]
-pub struct EquationElem {
- /// 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 $ <ratio>
- ///
- /// With @ratio, we get:
- /// $ F_n = floor(1 / sqrt(5) phi.alt^n) $
- /// ```
- pub numbering: Option<Numbering>,
-
- /// A supplement for the equation.
- ///
- /// For references to equations, this is added before the referenced number.
- ///
- /// If a function is specified, it is passed the referenced equation and
- /// should return content.
- ///
- /// ```example
- /// #set math.equation(numbering: "(1)", supplement: [Eq.])
- ///
- /// We define:
- /// $ phi.alt := (1 + sqrt(5)) / 2 $ <ratio>
- ///
- /// With @ratio, we get:
- /// $ F_n = floor(1 / sqrt(5) phi.alt^n) $
- /// ```
- pub supplement: Smart<Option<Supplement>>,
-
- /// The contents of the equation.
- #[required]
- pub body: Content,
-}
-
-impl Synthesize for EquationElem {
- fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
- // Resolve the supplement.
- let supplement = match self.supplement(styles) {
- Smart::Auto => TextElem::packed(self.local_name_in(styles)),
- Smart::Custom(None) => Content::empty(),
- Smart::Custom(Some(supplement)) => supplement.resolve(vt, [self.clone()])?,
- };
-
- self.push_block(self.block(styles));
- self.push_numbering(self.numbering(styles));
- self.push_supplement(Smart::Custom(Some(Supplement::Content(supplement))));
-
- Ok(())
- }
-}
-
-impl Show for EquationElem {
- #[tracing::instrument(name = "EquationElem::show", skip_all)]
- fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
- let mut realized = self.clone().pack().guarded(Guard::Base(Self::func()));
- if self.block(styles) {
- realized = realized.aligned(Axes::with_x(Some(Align::Center.into())))
- }
- Ok(realized)
- }
-}
-
-impl Finalize for EquationElem {
- fn finalize(&self, realized: Content, _: StyleChain) -> Content {
- realized
- .styled(TextElem::set_weight(FontWeight::from_number(450)))
- .styled(TextElem::set_font(FontList(vec![FontFamily::new(
- "New Computer Modern Math",
- )])))
- }
-}
-
-impl Layout for EquationElem {
- #[tracing::instrument(name = "EquationElem::layout", skip_all)]
- fn layout(
- &self,
- vt: &mut Vt,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- const NUMBER_GUTTER: Em = Em::new(0.5);
-
- let block = self.block(styles);
-
- // 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.as_str(), variant)?;
- let font = world.font(id)?;
- let _ = font.ttf().tables().math?.constants?;
- Some(font)
- })
- else {
- bail!(self.span(), "current font does not support math");
- };
-
- let mut ctx = MathContext::new(vt, styles, regions, &font, block);
- let mut frame = ctx.layout_frame(self)?;
-
- if block {
- if let Some(numbering) = self.numbering(styles) {
- let pod = Regions::one(regions.base(), Axes::splat(false));
- let counter = Counter::of(Self::func())
- .display(Some(numbering), false)
- .layout(vt, styles, pod)?
- .into_frame();
-
- let width = if regions.size.x.is_finite() {
- regions.size.x
- } else {
- frame.width()
- + 2.0 * (counter.width() + NUMBER_GUTTER.resolve(styles))
- };
-
- let height = frame.height().max(counter.height());
- frame.resize(Size::new(width, height), Align::CENTER_HORIZON);
-
- let x = if TextElem::dir_in(styles).is_positive() {
- frame.width() - counter.width()
- } else {
- Abs::zero()
- };
- let y = (frame.height() - counter.height()) / 2.0;
-
- frame.push_frame(Point::new(x, y), counter)
- }
- } else {
- let slack = ParElem::leading_in(styles) * 0.7;
- let top_edge = TextElem::top_edge_in(styles).resolve(styles, font.metrics());
- let bottom_edge =
- -TextElem::bottom_edge_in(styles).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;
- }
-
- // Apply metadata.
- frame.meta(styles, false);
-
- Ok(Fragment::frame(frame))
- }
-}
-
-impl Count for EquationElem {
- fn update(&self) -> Option<CounterUpdate> {
- (self.block(StyleChain::default())
- && self.numbering(StyleChain::default()).is_some())
- .then(|| CounterUpdate::Step(NonZeroUsize::ONE))
- }
-}
-
-impl LocalName for EquationElem {
- fn local_name(&self, lang: Lang, region: Option<Region>) -> &'static str {
- match lang {
- Lang::ALBANIAN => "Ekuacion",
- Lang::ARABIC => "معادلة",
- Lang::BOKMÅL => "Ligning",
- Lang::CHINESE if option_eq(region, "TW") => "方程式",
- Lang::CHINESE => "等式",
- Lang::CZECH => "Rovnice",
- Lang::DANISH => "Ligning",
- Lang::DUTCH => "Vergelijking",
- Lang::FILIPINO => "Ekwasyon",
- Lang::FRENCH => "Équation",
- Lang::GERMAN => "Gleichung",
- Lang::ITALIAN => "Equazione",
- Lang::NYNORSK => "Likning",
- Lang::POLISH => "Równanie",
- Lang::PORTUGUESE => "Equação",
- Lang::RUSSIAN => "Уравнение",
- Lang::SLOVENIAN => "Enačba",
- Lang::SPANISH => "Ecuación",
- Lang::SWEDISH => "Ekvation",
- Lang::TURKISH => "Denklem",
- Lang::UKRAINIAN => "Рівняння",
- Lang::VIETNAMESE => "Phương trình",
- Lang::ENGLISH | _ => "Equation",
- }
- }
-}
-
-impl Refable for EquationElem {
- fn supplement(&self) -> Content {
- // After synthesis, this should always be custom content.
- match self.supplement(StyleChain::default()) {
- Smart::Custom(Some(Supplement::Content(content))) => content,
- _ => Content::empty(),
- }
- }
-
- fn counter(&self) -> Counter {
- Counter::of(Self::func())
- }
-
- fn numbering(&self) -> Option<Numbering> {
- self.numbering(StyleChain::default())
- }
-}
-
-impl Outlinable for EquationElem {
- fn outline(&self, vt: &mut Vt) -> SourceResult<Option<Content>> {
- let Some(numbering) = self.numbering(StyleChain::default()) else {
- return Ok(None);
- };
-
- // After synthesis, this should always be custom content.
- let mut supplement = match self.supplement(StyleChain::default()) {
- Smart::Custom(Some(Supplement::Content(content))) => content,
- _ => Content::empty(),
- };
-
- if !supplement.is_empty() {
- supplement += TextElem::packed("\u{a0}");
- }
-
- let numbers = self
- .counter()
- .at(vt, self.0.location().unwrap())?
- .display(vt, &numbering)?;
-
- Ok(Some(supplement + numbers))
- }
-}
-
-pub trait LayoutMath {
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()>;
-}
-
-impl LayoutMath for EquationElem {
- #[tracing::instrument(skip(ctx))]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- self.body().layout_math(ctx)
- }
-}
-
-impl LayoutMath for Content {
- #[tracing::instrument(skip(ctx))]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- // Directly layout the body of nested equations instead of handling it
- // like a normal equation so that things like this work:
- // ```
- // #let my = $pi$
- // $ my r^2 $
- // ```
- if let Some(elem) = self.to::<EquationElem>() {
- return elem.layout_math(ctx);
- }
-
- if let Some(realized) = ctx.realize(self)? {
- return realized.layout_math(ctx);
- }
-
- if let Some(children) = self.to_sequence() {
- for child in children {
- child.layout_math(ctx)?;
- }
- return Ok(());
- }
-
- if let Some((elem, styles)) = self.to_styled() {
- if TextElem::font_in(ctx.styles().chain(styles))
- != TextElem::font_in(ctx.styles())
- {
- let frame = ctx.layout_content(self)?;
- ctx.push(FrameFragment::new(ctx, frame).with_spaced(true));
- return Ok(());
- }
-
- let prev_map = std::mem::replace(&mut ctx.local, styles.clone());
- let prev_size = ctx.size;
- ctx.local.apply(prev_map.clone());
- ctx.size = TextElem::size_in(ctx.styles());
- elem.layout_math(ctx)?;
- ctx.size = prev_size;
- ctx.local = prev_map;
- return Ok(());
- }
-
- if self.is::<SpaceElem>() {
- ctx.push(MathFragment::Space(ctx.space_width.scaled(ctx)));
- return Ok(());
- }
-
- if self.is::<LinebreakElem>() {
- ctx.push(MathFragment::Linebreak);
- return Ok(());
- }
-
- if let Some(elem) = self.to::<HElem>() {
- if let Spacing::Rel(rel) = elem.amount() {
- if rel.rel.is_zero() {
- ctx.push(MathFragment::Spacing(rel.abs.resolve(ctx.styles())));
- }
- }
- return Ok(());
- }
-
- if let Some(elem) = self.to::<TextElem>() {
- let fragment = ctx.layout_text(elem)?;
- ctx.push(fragment);
- return Ok(());
- }
-
- if let Some(elem) = self.with::<dyn LayoutMath>() {
- return elem.layout_math(ctx);
- }
-
- let mut frame = ctx.layout_content(self)?;
- if !frame.has_baseline() {
- let axis = scaled!(ctx, axis_height);
- frame.set_baseline(frame.height() / 2.0 + axis);
- }
- ctx.push(FrameFragment::new(ctx, frame).with_spaced(true));
-
- Ok(())
- }
-}
diff --git a/library/src/math/op.rs b/library/src/math/op.rs
deleted file mode 100644
index 8ad74c49..00000000
--- a/library/src/math/op.rs
+++ /dev/null
@@ -1,113 +0,0 @@
-use typst::eval::Scope;
-
-use super::*;
-
-/// A text operator in an equation.
-///
-/// ## Example { #example }
-/// ```example
-/// $ tan x = (sin x)/(cos x) $
-/// $ op("custom",
-/// limits: #true)_(n->oo) n $
-/// ```
-///
-/// ## Predefined Operators { #predefined }
-/// Typst predefines the operators `arccos`, `arcsin`, `arctan`, `arg`,
-/// `cos`, `cosh`, `cot`, `ctg`, `coth`, `csc`, `deg`, `det`, `dim`,
-/// `exp`, `gcd`, `hom`, `mod`, `inf`, `ker`, `lg`, `lim`, `ln`, `log`,
-/// `max`, `min`, `Pr`, `sec`, `sin`, `sinc`, `sinh`, `sup`, `tan`, `tg`,
-/// `tanh`, `liminf`, and `limsup`.
-///
-/// Display: Text Operator
-/// Category: math
-#[element(LayoutMath)]
-pub struct OpElem {
- /// The operator's text.
- #[required]
- pub text: EcoString,
-
- /// Whether the operator should show attachments as limits in display mode.
- #[default(false)]
- pub limits: bool,
-}
-
-impl LayoutMath for OpElem {
- #[tracing::instrument(skip(ctx))]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- let fragment =
- ctx.layout_text(&TextElem::new(self.text()).spanned(self.span()))?;
- ctx.push(
- FrameFragment::new(ctx, fragment.into_frame())
- .with_class(MathClass::Large)
- .with_limits(if self.limits(ctx.styles()) {
- Limits::Display
- } else {
- Limits::Never
- }),
- );
- Ok(())
- }
-}
-
-macro_rules! ops {
- ($($name:ident $(: $value:literal)? $(($tts:tt))?),* $(,)?) => {
- pub(super) fn define(math: &mut Scope) {
- $(math.define(
- stringify!($name),
- OpElem::new(ops!(@name $name $(: $value)?).into())
- .with_limits(ops!(@limit $($tts)*))
- .pack()
- );)*
-
- let dif = |d| {
- HElem::new(THIN.into()).pack()
- + MathStyleElem::new(TextElem::packed(d)).with_italic(Some(false)).pack()
- };
- math.define("dif", dif('d'));
- math.define("Dif", dif('D'));
- }
- };
- (@name $name:ident) => { stringify!($name) };
- (@name $name:ident: $value:literal) => { $value };
- (@limit limits) => { true };
- (@limit) => { false };
-}
-
-ops! {
- arccos,
- arcsin,
- arctan,
- arg,
- cos,
- cosh,
- cot,
- ctg,
- coth,
- csc,
- deg,
- det (limits),
- dim,
- exp,
- gcd (limits),
- hom,
- mod,
- inf (limits),
- ker,
- lg,
- lim (limits),
- ln,
- log,
- max (limits),
- min (limits),
- Pr (limits),
- sec,
- sin,
- sinc,
- sinh,
- sup (limits),
- tan,
- tg,
- tanh,
- liminf: "lim inf" (limits),
- limsup: "lim sup" (limits),
-}
diff --git a/library/src/math/root.rs b/library/src/math/root.rs
deleted file mode 100644
index d1c5f46a..00000000
--- a/library/src/math/root.rs
+++ /dev/null
@@ -1,156 +0,0 @@
-use super::*;
-
-/// A square root.
-///
-/// ## Example { #example }
-/// ```example
-/// $ sqrt(x^2) = x = sqrt(x)^2 $
-/// ```
-///
-/// Display: Square Root
-/// Category: math
-#[func]
-pub fn sqrt(
- /// The expression to take the square root of.
- radicand: Content,
-) -> Content {
- RootElem::new(radicand).pack()
-}
-
-/// A general root.
-///
-/// ## Example { #example }
-/// ```example
-/// $ root(3, x) $
-/// ```
-///
-/// Display: Root
-/// Category: math
-#[element(LayoutMath)]
-pub struct RootElem {
- /// Which root of the radicand to take.
- #[positional]
- pub index: Option<Content>,
-
- /// The expression to take the root of.
- #[required]
- pub radicand: Content,
-}
-
-impl LayoutMath for RootElem {
- #[tracing::instrument(skip(ctx))]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- layout(ctx, self.index(ctx.styles()).as_ref(), &self.radicand(), self.span())
- }
-}
-
-/// Layout a root.
-///
-/// https://www.w3.org/TR/mathml-core/#radicals-msqrt-mroot
-fn layout(
- ctx: &mut MathContext,
- mut index: Option<&Content>,
- radicand: &Content,
- span: Span,
-) -> SourceResult<()> {
- let gap = scaled!(
- ctx,
- text: radical_vertical_gap,
- display: radical_display_style_vertical_gap,
- );
- let thickness = scaled!(ctx, radical_rule_thickness);
- let extra_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_factor = 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, '√', span);
- glyph.stretch_vertical(ctx, target, Abs::zero()).frame
- });
-
- // Layout the index.
- // Script-script style looks too small, we use Script style instead.
- ctx.style(ctx.style.with_size(MathSize::Script));
- let index = index.map(|elem| ctx.layout_frame(elem)).transpose()?;
- ctx.unstyle();
-
- let gap = gap.max((sqrt.height() - radicand.height() - thickness) / 2.0);
- let descent = radicand.descent() + gap;
- let inner_ascent = extra_ascender + thickness + gap + radicand.ascent();
-
- let mut sqrt_offset = Abs::zero();
- let mut shift_up = Abs::zero();
- let mut ascent = inner_ascent;
-
- if let Some(index) = &index {
- sqrt_offset = kern_before + index.width() + kern_after;
- shift_up = raise_factor * sqrt.height() - descent + index.descent();
- ascent.set_max(shift_up + index.ascent());
- }
-
- let radicant_offset = sqrt_offset + sqrt.width();
- let width = radicant_offset + radicand.width();
- let size = Size::new(width, ascent + descent);
-
- let sqrt_pos = Point::new(sqrt_offset, ascent - inner_ascent);
- let line_pos = Point::new(radicant_offset, ascent - inner_ascent + thickness / 2.0);
- let radicand_pos = Point::new(radicant_offset, ascent - radicand.ascent());
-
- let mut frame = Frame::new(size);
- frame.set_baseline(ascent);
-
- if let Some(index) = index {
- let index_pos = Point::new(kern_before, ascent - shift_up - index.ascent());
- frame.push_frame(index_pos, index);
- }
-
- frame.push_frame(sqrt_pos, sqrt);
- frame.push(
- line_pos,
- FrameItem::Shape(
- Geometry::Line(Point::with_x(radicand.width())).stroked(Stroke {
- paint: TextElem::fill_in(ctx.styles()),
- thickness,
- ..Stroke::default()
- }),
- span,
- ),
- );
-
- frame.push_frame(radicand_pos, radicand);
- ctx.push(FrameFragment::new(ctx, frame));
-
- Ok(())
-}
-
-/// Select a precomposed radical, if the font has it.
-fn precomposed(ctx: &MathContext, index: Option<&Content>, target: Abs) -> Option<Frame> {
- let elem = index?.to::<TextElem>()?;
- let c = match elem.text().as_str() {
- "3" => '∛',
- "4" => '∜',
- _ => return None,
- };
-
- ctx.ttf.glyph_index(c)?;
- let glyph = GlyphFragment::new(ctx, c, elem.span());
- let variant = glyph.stretch_vertical(ctx, target, Abs::zero()).frame;
- if variant.height() < target {
- return None;
- }
-
- Some(variant)
-}
diff --git a/library/src/math/row.rs b/library/src/math/row.rs
deleted file mode 100644
index 687f82b8..00000000
--- a/library/src/math/row.rs
+++ /dev/null
@@ -1,258 +0,0 @@
-use std::iter::once;
-
-use crate::layout::AlignElem;
-
-use super::*;
-
-pub const TIGHT_LEADING: Em = Em::new(0.25);
-
-#[derive(Debug, Default, Clone)]
-pub struct MathRow(Vec<MathFragment>);
-
-impl MathRow {
- pub fn new(fragments: Vec<MathFragment>) -> Self {
- let iter = fragments.into_iter().peekable();
- let mut last: Option<usize> = None;
- let mut space: Option<MathFragment> = None;
- let mut resolved: Vec<MathFragment> = vec![];
-
- for mut fragment in iter {
- match fragment {
- // Keep space only if supported by spaced fragments.
- MathFragment::Space(_) => {
- if last.is_some() {
- space = Some(fragment);
- }
- continue;
- }
-
- // Explicit spacing disables automatic spacing.
- MathFragment::Spacing(_) => {
- last = None;
- space = None;
- resolved.push(fragment);
- continue;
- }
-
- // Alignment points are resolved later.
- MathFragment::Align => {
- resolved.push(fragment);
- continue;
- }
-
- // New line, new things.
- MathFragment::Linebreak => {
- resolved.push(fragment);
- space = None;
- last = None;
- continue;
- }
-
- _ => {}
- }
-
- // Convert variable operators into binary operators if something
- // precedes them and they are not preceded by a operator or comparator.
- if fragment.class() == Some(MathClass::Vary)
- && matches!(
- last.and_then(|i| resolved[i].class()),
- Some(
- MathClass::Normal
- | MathClass::Alphabetic
- | MathClass::Closing
- | MathClass::Fence
- )
- )
- {
- fragment.set_class(MathClass::Binary);
- }
-
- // Insert spacing between the last and this item.
- if let Some(i) = last {
- if let Some(s) = spacing(&resolved[i], space.take(), &fragment) {
- resolved.insert(i + 1, s);
- }
- }
-
- last = Some(resolved.len());
- resolved.push(fragment);
- }
-
- Self(resolved)
- }
-
- pub fn iter(&self) -> std::slice::Iter<'_, MathFragment> {
- self.0.iter()
- }
-
- /// Extract the sublines of the row.
- ///
- /// It is very unintuitive, but in current state of things, a `MathRow` can
- /// contain several actual rows. That function deconstructs it to "single"
- /// rows. Hopefully this is only a temporary hack.
- pub fn rows(&self) -> Vec<Self> {
- self.0
- .split(|frag| matches!(frag, MathFragment::Linebreak))
- .map(|slice| Self(slice.to_vec()))
- .collect()
- }
-
- pub fn ascent(&self) -> Abs {
- self.iter().map(MathFragment::ascent).max().unwrap_or_default()
- }
-
- pub fn descent(&self) -> Abs {
- self.iter().map(MathFragment::descent).max().unwrap_or_default()
- }
-
- pub fn class(&self) -> MathClass {
- // Predict the class of the output of 'into_fragment'
- if self.0.len() == 1 {
- self.0
- .first()
- .and_then(|fragment| fragment.class())
- .unwrap_or(MathClass::Special)
- } else {
- // FrameFragment::new() (inside 'into_fragment' in this branch) defaults
- // to MathClass::Normal for its class.
- MathClass::Normal
- }
- }
-
- pub fn into_frame(self, ctx: &MathContext) -> Frame {
- let styles = ctx.styles();
- let align = AlignElem::alignment_in(styles).x.resolve(styles);
- self.into_aligned_frame(ctx, &[], align)
- }
-
- pub fn into_fragment(self, ctx: &MathContext) -> MathFragment {
- if self.0.len() == 1 {
- self.0.into_iter().next().unwrap()
- } else {
- FrameFragment::new(ctx, self.into_frame(ctx)).into()
- }
- }
-
- pub fn into_aligned_frame(
- self,
- ctx: &MathContext,
- points: &[Abs],
- align: Align,
- ) -> Frame {
- if self.iter().any(|frag| matches!(frag, MathFragment::Linebreak)) {
- let leading = if ctx.style.size >= MathSize::Text {
- ParElem::leading_in(ctx.styles())
- } else {
- TIGHT_LEADING.scaled(ctx)
- };
-
- let mut rows: Vec<_> = self.rows();
-
- if matches!(rows.last(), Some(row) if row.0.is_empty()) {
- rows.pop();
- }
-
- let AlignmentResult { points, width } = alignments(&rows);
- let mut frame = Frame::new(Size::zero());
-
- for (i, row) in rows.into_iter().enumerate() {
- let sub = row.into_line_frame(&points, align);
- let size = frame.size_mut();
- if i > 0 {
- size.y += leading;
- }
-
- let mut pos = Point::with_y(size.y);
- if points.is_empty() {
- pos.x = align.position(width - sub.width());
- }
- size.y += sub.height();
- size.x.set_max(sub.width());
- frame.push_frame(pos, sub);
- }
- frame
- } else {
- self.into_line_frame(points, align)
- }
- }
-
- fn into_line_frame(self, points: &[Abs], align: Align) -> Frame {
- let ascent = self.ascent();
- let mut frame = Frame::new(Size::new(Abs::zero(), ascent + self.descent()));
- frame.set_baseline(ascent);
-
- let mut next_x = {
- let mut widths = Vec::new();
- if !points.is_empty() && align != Align::Left {
- let mut width = Abs::zero();
- for fragment in self.iter() {
- if matches!(fragment, MathFragment::Align) {
- widths.push(width);
- width = Abs::zero();
- } else {
- width += fragment.width();
- }
- }
- widths.push(width);
- }
- let widths = widths;
-
- let mut prev_points = once(Abs::zero()).chain(points.iter().copied());
- let mut point_widths = points.iter().copied().zip(widths);
- let mut alternator = LeftRightAlternator::Right;
- move || match align {
- Align::Left => prev_points.next(),
- Align::Right => point_widths.next().map(|(point, width)| point - width),
- _ => point_widths
- .next()
- .zip(prev_points.next())
- .zip(alternator.next())
- .map(|(((point, width), prev_point), alternator)| match alternator {
- LeftRightAlternator::Left => prev_point,
- LeftRightAlternator::Right => point - width,
- }),
- }
- };
- let mut x = next_x().unwrap_or_default();
-
- for fragment in self.0.into_iter() {
- if matches!(fragment, MathFragment::Align) {
- x = next_x().unwrap_or(x);
- continue;
- }
-
- let y = ascent - fragment.ascent();
- let pos = Point::new(x, y);
- x += fragment.width();
- frame.push_frame(pos, fragment.into_frame());
- }
-
- frame.size_mut().x = x;
- frame
- }
-}
-
-impl<T: Into<MathFragment>> From<T> for MathRow {
- fn from(fragment: T) -> Self {
- Self(vec![fragment.into()])
- }
-}
-
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-enum LeftRightAlternator {
- Left,
- Right,
-}
-
-impl Iterator for LeftRightAlternator {
- type Item = LeftRightAlternator;
-
- fn next(&mut self) -> Option<Self::Item> {
- let r = Some(*self);
- match self {
- Self::Left => *self = Self::Right,
- Self::Right => *self = Self::Left,
- }
- r
- }
-}
diff --git a/library/src/math/spacing.rs b/library/src/math/spacing.rs
deleted file mode 100644
index 848aca78..00000000
--- a/library/src/math/spacing.rs
+++ /dev/null
@@ -1,60 +0,0 @@
-use super::*;
-
-pub(super) const THIN: Em = Em::new(1.0 / 6.0);
-pub(super) const MEDIUM: Em = Em::new(2.0 / 9.0);
-pub(super) const THICK: Em = Em::new(5.0 / 18.0);
-pub(super) const QUAD: Em = Em::new(1.0);
-
-/// Hook up all spacings.
-pub(super) fn define(math: &mut Scope) {
- math.define("thin", HElem::new(THIN.into()).pack());
- math.define("med", HElem::new(MEDIUM.into()).pack());
- math.define("thick", HElem::new(THICK.into()).pack());
- math.define("quad", HElem::new(QUAD.into()).pack());
-}
-
-/// Create the spacing between two fragments in a given style.
-pub(super) fn spacing(
- l: &MathFragment,
- space: Option<MathFragment>,
- r: &MathFragment,
-) -> Option<MathFragment> {
- use MathClass::*;
-
- let class = |f: &MathFragment| f.class().unwrap_or(Special);
- let resolve = |v: Em, f: &MathFragment| {
- Some(MathFragment::Spacing(f.font_size().map_or(Abs::zero(), |size| v.at(size))))
- };
- let script =
- |f: &MathFragment| f.style().map_or(false, |s| s.size <= MathSize::Script);
-
- match (class(l), class(r)) {
- // No spacing before punctuation; thin spacing after punctuation, unless
- // in script size.
- (_, Punctuation) => None,
- (Punctuation, _) if !script(l) => resolve(THIN, l),
-
- // No spacing after opening delimiters and before closing delimiters.
- (Opening, _) | (_, Closing) => None,
-
- // Thick spacing around relations, unless followed by a another relation
- // or in script size.
- (Relation, Relation) => None,
- (Relation, _) if !script(l) => resolve(THICK, l),
- (_, Relation) if !script(r) => resolve(THICK, r),
-
- // Medium spacing around binary operators, unless in script size.
- (Binary, _) if !script(l) => resolve(MEDIUM, l),
- (_, Binary) if !script(r) => resolve(MEDIUM, r),
-
- // Thin spacing around large operators, unless next to a delimiter.
- (Large, Opening | Fence) | (Closing | Fence, Large) => None,
- (Large, _) => resolve(THIN, l),
- (_, Large) => resolve(THIN, r),
-
- // Spacing around spaced frames.
- _ if (l.is_spaced() || r.is_spaced()) => space,
-
- _ => None,
- }
-}
diff --git a/library/src/math/stretch.rs b/library/src/math/stretch.rs
deleted file mode 100644
index 910f7a81..00000000
--- a/library/src/math/stretch.rs
+++ /dev/null
@@ -1,199 +0,0 @@
-use ttf_parser::math::{GlyphAssembly, GlyphConstruction, GlyphPart};
-use ttf_parser::LazyArray16;
-
-use super::*;
-
-/// Maximum number of times extenders can be repeated.
-const MAX_REPEATS: usize = 1024;
-
-impl GlyphFragment {
- /// Try to stretch a glyph to a desired height.
- pub fn stretch_vertical(
- self,
- ctx: &MathContext,
- height: Abs,
- short_fall: Abs,
- ) -> VariantFragment {
- stretch_glyph(ctx, self, height, short_fall, false)
- }
-
- /// Try to stretch a glyph to a desired width.
- pub fn stretch_horizontal(
- self,
- ctx: &MathContext,
- width: Abs,
- short_fall: Abs,
- ) -> VariantFragment {
- stretch_glyph(ctx, self, width, short_fall, true)
- }
-}
-
-/// Try to stretch a glyph to a desired width or height.
-///
-/// The resulting frame may not have the exact desired width.
-fn stretch_glyph(
- ctx: &MathContext,
- mut base: GlyphFragment,
- target: Abs,
- short_fall: Abs,
- horizontal: bool,
-) -> VariantFragment {
- let short_target = target - short_fall;
- let mut min_overlap = Abs::zero();
- let construction = ctx
- .table
- .variants
- .and_then(|variants| {
- min_overlap = variants.min_connector_overlap.scaled(ctx);
- if horizontal {
- variants.horizontal_constructions
- } else {
- variants.vertical_constructions
- }
- .get(base.id)
- })
- .unwrap_or(GlyphConstruction { assembly: None, variants: LazyArray16::new(&[]) });
-
- // If the base glyph is good enough, use it.
- let advance = if horizontal { base.width } else { base.height() };
- if short_target <= advance {
- return base.into_variant();
- }
-
- // Search for a pre-made variant with a good advance.
- let mut best_id = base.id;
- let mut best_advance = base.width;
- for variant in construction.variants {
- best_id = variant.variant_glyph;
- best_advance = base.font.to_em(variant.advance_measurement).at(base.font_size);
- if short_target <= best_advance {
- break;
- }
- }
-
- // This is either good or the best we've got.
- if short_target <= best_advance || construction.assembly.is_none() {
- base.set_id(ctx, best_id);
- return base.into_variant();
- }
-
- // Assemble from parts.
- let assembly = construction.assembly.unwrap();
- assemble(ctx, base, assembly, min_overlap, target, horizontal)
-}
-
-/// Assemble a glyph from parts.
-fn assemble(
- ctx: &MathContext,
- base: GlyphFragment,
- assembly: GlyphAssembly,
- min_overlap: Abs,
- target: Abs,
- horizontal: bool,
-) -> VariantFragment {
- // Determine the number of times the extenders need to be repeated as well
- // as a ratio specifying how much to spread the parts apart
- // (0 = maximal overlap, 1 = minimal overlap).
- let mut full;
- let mut ratio;
- let mut repeat = 0;
- loop {
- full = Abs::zero();
- ratio = 0.0;
-
- let mut parts = parts(assembly, repeat).peekable();
- let mut growable = Abs::zero();
-
- while let Some(part) = parts.next() {
- let mut advance = part.full_advance.scaled(ctx);
- if let Some(next) = parts.peek() {
- let max_overlap = part
- .end_connector_length
- .min(next.start_connector_length)
- .scaled(ctx);
-
- advance -= max_overlap;
- growable += max_overlap - min_overlap;
- }
-
- full += advance;
- }
-
- if full < target {
- let delta = target - full;
- ratio = (delta / growable).min(1.0);
- full += ratio * growable;
- }
-
- if target <= full || repeat >= MAX_REPEATS {
- break;
- }
-
- repeat += 1;
- }
-
- let mut selected = vec![];
- let mut parts = parts(assembly, repeat).peekable();
- while let Some(part) = parts.next() {
- let mut advance = part.full_advance.scaled(ctx);
- if let Some(next) = parts.peek() {
- let max_overlap =
- part.end_connector_length.min(next.start_connector_length).scaled(ctx);
- advance -= max_overlap;
- advance += ratio * (max_overlap - min_overlap);
- }
-
- let mut fragment = base.clone();
- fragment.set_id(ctx, part.glyph_id);
- selected.push((fragment, advance));
- }
-
- let size;
- let baseline;
- if horizontal {
- let height = base.ascent + base.descent;
- size = Size::new(full, height);
- baseline = base.ascent;
- } else {
- let axis = scaled!(ctx, axis_height);
- let width = selected.iter().map(|(f, _)| f.width).max().unwrap_or_default();
- size = Size::new(width, full);
- baseline = full / 2.0 + axis;
- }
-
- let mut frame = Frame::new(size);
- let mut offset = Abs::zero();
- frame.set_baseline(baseline);
- frame.meta_iter(base.meta);
-
- for (fragment, advance) in selected {
- let pos = if horizontal {
- Point::new(offset, frame.baseline() - fragment.ascent)
- } else {
- Point::with_y(full - offset - fragment.height())
- };
- frame.push_frame(pos, fragment.into_frame());
- offset += advance;
- }
-
- VariantFragment {
- c: base.c,
- id: None,
- frame,
- style: base.style,
- font_size: base.font_size,
- italics_correction: Abs::zero(),
- class: base.class,
- span: base.span,
- limits: base.limits,
- }
-}
-
-/// Return an iterator over the assembly's parts with extenders repeated the
-/// specified number of times.
-fn parts(assembly: GlyphAssembly, repeat: usize) -> impl Iterator<Item = GlyphPart> + '_ {
- assembly.parts.into_iter().flat_map(move |part| {
- let count = if part.part_flags.extender() { repeat } else { 1 };
- std::iter::repeat(part).take(count)
- })
-}
diff --git a/library/src/math/style.rs b/library/src/math/style.rs
deleted file mode 100644
index 235770db..00000000
--- a/library/src/math/style.rs
+++ /dev/null
@@ -1,620 +0,0 @@
-use super::*;
-
-/// Bold font style in math.
-///
-/// ## Example { #example }
-/// ```example
-/// $ bold(A) := B^+ $
-/// ```
-///
-/// Display: Bold
-/// Category: math
-#[func]
-pub fn bold(
- /// The content to style.
- body: Content,
-) -> Content {
- MathStyleElem::new(body).with_bold(Some(true)).pack()
-}
-
-/// Upright (non-italic) font style in math.
-///
-/// ## Example { #example }
-/// ```example
-/// $ upright(A) != A $
-/// ```
-///
-/// Display: Upright
-/// Category: math
-#[func]
-pub fn upright(
- /// The content to style.
- body: Content,
-) -> Content {
- MathStyleElem::new(body).with_italic(Some(false)).pack()
-}
-
-/// Italic font style in math.
-///
-/// For roman letters and greek lowercase letters, this is already the default.
-///
-/// Display: Italic
-/// Category: math
-#[func]
-pub fn italic(
- /// The content to style.
- body: Content,
-) -> Content {
- MathStyleElem::new(body).with_italic(Some(true)).pack()
-}
-/// Serif (roman) font style in math.
-///
-/// This is already the default.
-///
-/// Display: Serif
-/// Category: math
-#[func]
-pub fn serif(
- /// The content to style.
- body: Content,
-) -> Content {
- MathStyleElem::new(body).with_variant(Some(MathVariant::Serif)).pack()
-}
-
-/// Sans-serif font style in math.
-///
-/// ## Example { #example }
-/// ```example
-/// $ sans(A B C) $
-/// ```
-///
-/// Display: Sans-serif
-/// Category: math
-#[func]
-pub fn sans(
- /// The content to style.
- body: Content,
-) -> Content {
- MathStyleElem::new(body).with_variant(Some(MathVariant::Sans)).pack()
-}
-
-/// Calligraphic font style in math.
-///
-/// ## Example { #example }
-/// ```example
-/// Let $cal(P)$ be the set of ...
-/// ```
-///
-/// Display: Calligraphic
-/// Category: math
-#[func]
-pub fn cal(
- /// The content to style.
- body: Content,
-) -> Content {
- MathStyleElem::new(body).with_variant(Some(MathVariant::Cal)).pack()
-}
-
-/// Fraktur font style in math.
-///
-/// ## Example { #example }
-/// ```example
-/// $ frak(P) $
-/// ```
-///
-/// Display: Fraktur
-/// Category: math
-#[func]
-pub fn frak(
- /// The content to style.
- body: Content,
-) -> Content {
- MathStyleElem::new(body).with_variant(Some(MathVariant::Frak)).pack()
-}
-
-/// Monospace font style in math.
-///
-/// ## Example { #example }
-/// ```example
-/// $ mono(x + y = z) $
-/// ```
-///
-/// Display: Monospace
-/// Category: math
-#[func]
-pub fn mono(
- /// The content to style.
- body: Content,
-) -> Content {
- MathStyleElem::new(body).with_variant(Some(MathVariant::Mono)).pack()
-}
-
-/// Blackboard bold (double-struck) font style in math.
-///
-/// For uppercase latin letters, blackboard bold is additionally available
-/// through [symbols]($category/symbols/sym) of the form `NN` and `RR`.
-///
-/// ## Example { #example }
-/// ```example
-/// $ bb(b) $
-/// $ bb(N) = NN $
-/// $ f: NN -> RR $
-/// ```
-///
-/// Display: Blackboard Bold
-/// Category: math
-#[func]
-pub fn bb(
- /// The content to style.
- body: Content,
-) -> Content {
- MathStyleElem::new(body).with_variant(Some(MathVariant::Bb)).pack()
-}
-
-/// Forced display style in math.
-///
-/// This is the normal size for block equations.
-///
-/// ## Example { #example }
-/// ```example
-/// $sum_i x_i/2 = display(sum_i x_i/2)$
-/// ```
-///
-/// Display: Display Size
-/// Category: math
-#[func]
-pub fn display(
- /// The content to size.
- body: Content,
- /// Whether to impose a height restriction for exponents, like regular sub-
- /// and superscripts do.
- #[named]
- #[default(false)]
- cramped: bool,
-) -> Content {
- MathStyleElem::new(body)
- .with_size(Some(MathSize::Display))
- .with_cramped(Some(cramped))
- .pack()
-}
-
-/// Forced inline (text) style in math.
-///
-/// This is the normal size for inline equations.
-///
-/// ## Example { #example }
-/// ```example
-/// $ sum_i x_i/2
-/// = inline(sum_i x_i/2) $
-/// ```
-///
-/// Display: Inline Size
-/// Category: math
-#[func]
-pub fn inline(
- /// The content to size.
- body: Content,
- /// Whether to impose a height restriction for exponents, like regular sub-
- /// and superscripts do.
- #[named]
- #[default(false)]
- cramped: bool,
-) -> Content {
- MathStyleElem::new(body)
- .with_size(Some(MathSize::Text))
- .with_cramped(Some(cramped))
- .pack()
-}
-
-/// Forced script style in math.
-///
-/// This is the smaller size used in powers or sub- or superscripts.
-///
-/// ## Example { #example }
-/// ```example
-/// $sum_i x_i/2 = script(sum_i x_i/2)$
-/// ```
-///
-/// Display: Script Size
-/// Category: math
-#[func]
-pub fn script(
- /// The content to size.
- body: Content,
- /// Whether to impose a height restriction for exponents, like regular sub-
- /// and superscripts do.
- #[named]
- #[default(true)]
- cramped: bool,
-) -> Content {
- MathStyleElem::new(body)
- .with_size(Some(MathSize::Script))
- .with_cramped(Some(cramped))
- .pack()
-}
-
-/// Forced second script style in math.
-///
-/// This is the smallest size, used in second-level sub- and superscripts
-/// (script of the script).
-///
-/// ## Example { #example }
-/// ```example
-/// $sum_i x_i/2 = sscript(sum_i x_i/2)$
-/// ```
-///
-/// Display: Script-Script Size
-/// Category: math
-#[func]
-pub fn sscript(
- /// The content to size.
- body: Content,
- /// Whether to impose a height restriction for exponents, like regular sub-
- /// and superscripts do.
- #[named]
- #[default(true)]
- cramped: bool,
-) -> Content {
- MathStyleElem::new(body)
- .with_size(Some(MathSize::ScriptScript))
- .with_cramped(Some(cramped))
- .pack()
-}
-
-/// A font variant in math.
-///
-/// Display: Bold
-/// Category: math
-#[element(LayoutMath)]
-pub struct MathStyleElem {
- /// The content to style.
- #[required]
- pub body: Content,
-
- /// The variant to select.
- pub variant: Option<MathVariant>,
-
- /// Whether to use bold glyphs.
- pub bold: Option<bool>,
-
- /// Whether to use italic glyphs.
- pub italic: Option<bool>,
-
- /// Whether to use forced size
- pub size: Option<MathSize>,
-
- /// Whether to limit height of exponents
- pub cramped: Option<bool>,
-}
-
-impl LayoutMath for MathStyleElem {
- #[tracing::instrument(skip(ctx))]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- let mut style = ctx.style;
- if let Some(variant) = self.variant(StyleChain::default()) {
- style = style.with_variant(variant);
- }
- if let Some(bold) = self.bold(StyleChain::default()) {
- style = style.with_bold(bold);
- }
- if let Some(italic) = self.italic(StyleChain::default()) {
- style = style.with_italic(italic);
- }
- if let Some(size) = self.size(StyleChain::default()) {
- style = style.with_size(size);
- }
- if let Some(cramped) = self.cramped(StyleChain::default()) {
- style = style.with_cramped(cramped);
- }
- ctx.style(style);
- self.body().layout_math(ctx)?;
- ctx.unstyle();
- Ok(())
- }
-}
-
-/// Text properties in math.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub struct MathStyle {
- /// The style variant to select.
- pub variant: MathVariant,
- /// The size of the glyphs.
- pub size: MathSize,
- /// Affects the height of exponents.
- pub cramped: bool,
- /// Whether to use bold glyphs.
- pub bold: bool,
- /// Whether to use italic glyphs.
- pub italic: Smart<bool>,
-}
-
-impl MathStyle {
- /// This style, with the given `variant`.
- pub fn with_variant(self, variant: MathVariant) -> Self {
- Self { variant, ..self }
- }
-
- /// This style, with the given `size`.
- pub fn with_size(self, size: MathSize) -> Self {
- Self { size, ..self }
- }
-
- /// This style, with `cramped` set to the given value.
- pub fn with_cramped(self, cramped: bool) -> Self {
- Self { cramped, ..self }
- }
-
- /// This style, with `bold` set to the given value.
- pub fn with_bold(self, bold: bool) -> Self {
- Self { bold, ..self }
- }
-
- /// This style, with `italic` set to the given value.
- pub fn with_italic(self, italic: bool) -> Self {
- Self { italic: Smart::Custom(italic), ..self }
- }
-
- /// The style for subscripts in the current style.
- pub fn for_subscript(self) -> Self {
- self.for_superscript().with_cramped(true)
- }
-
- /// The style for superscripts in the current style.
- pub fn for_superscript(self) -> Self {
- self.with_size(match self.size {
- MathSize::Display | MathSize::Text => MathSize::Script,
- MathSize::Script | MathSize::ScriptScript => MathSize::ScriptScript,
- })
- }
-
- /// The style for numerators in the current style.
- pub fn for_numerator(self) -> Self {
- self.with_size(match self.size {
- MathSize::Display => MathSize::Text,
- MathSize::Text => MathSize::Script,
- MathSize::Script | MathSize::ScriptScript => MathSize::ScriptScript,
- })
- }
-
- /// The style for denominators in the current style.
- pub fn for_denominator(self) -> Self {
- self.for_numerator().with_cramped(true)
- }
-
- /// Apply the style to a character.
- pub fn styled_char(self, c: char) -> char {
- styled_char(self, c)
- }
-}
-
-/// The size of elements in an equation.
-///
-/// See the TeXbook p. 141.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Cast)]
-pub enum MathSize {
- /// Second-level sub- and superscripts.
- ScriptScript,
- /// Sub- and superscripts.
- Script,
- /// Math in text.
- Text,
- /// Math on its own line.
- Display,
-}
-
-impl MathSize {
- pub(super) fn factor(self, ctx: &MathContext) -> f64 {
- match self {
- Self::Display | Self::Text => 1.0,
- Self::Script => percent!(ctx, script_percent_scale_down),
- Self::ScriptScript => percent!(ctx, script_script_percent_scale_down),
- }
- }
-}
-
-/// A mathematical style variant, as defined by Unicode.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Cast)]
-pub enum MathVariant {
- Serif,
- Sans,
- Cal,
- Frak,
- Mono,
- Bb,
-}
-
-impl Default for MathVariant {
- fn default() -> Self {
- Self::Serif
- }
-}
-
-/// Select the correct styled math letter.
-///
-/// https://www.w3.org/TR/mathml-core/#new-text-transform-mappings
-/// https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols
-pub(super) fn styled_char(style: MathStyle, c: char) -> char {
- use MathVariant::*;
-
- let MathStyle { variant, bold, .. } = style;
- let italic = style.italic.unwrap_or(matches!(
- c,
- 'a'..='z' | 'ı' | 'ȷ' | 'A'..='Z' | 'α'..='ω' |
- '∂' | 'ϵ' | 'ϑ' | 'ϰ' | 'ϕ' | 'ϱ' | 'ϖ'
- ));
-
- if let Some(c) = basic_exception(c) {
- return c;
- }
-
- if let Some(c) = latin_exception(c, variant, bold, italic) {
- return c;
- }
-
- if let Some(c) = greek_exception(c, variant, bold, italic) {
- return c;
- }
-
- let base = match c {
- 'A'..='Z' => 'A',
- 'a'..='z' => 'a',
- 'Α'..='Ω' => 'Α',
- 'α'..='ω' => 'α',
- '0'..='9' => '0',
- _ => return c,
- };
-
- let tuple = (variant, bold, italic);
- let start = match c {
- // Latin upper.
- 'A'..='Z' => match tuple {
- (Serif, false, false) => 0x0041,
- (Serif, true, false) => 0x1D400,
- (Serif, false, true) => 0x1D434,
- (Serif, true, true) => 0x1D468,
- (Sans, false, false) => 0x1D5A0,
- (Sans, true, false) => 0x1D5D4,
- (Sans, false, true) => 0x1D608,
- (Sans, true, true) => 0x1D63C,
- (Cal, false, _) => 0x1D49C,
- (Cal, true, _) => 0x1D4D0,
- (Frak, false, _) => 0x1D504,
- (Frak, true, _) => 0x1D56C,
- (Mono, _, _) => 0x1D670,
- (Bb, _, _) => 0x1D538,
- },
-
- // Latin lower.
- 'a'..='z' => match tuple {
- (Serif, false, false) => 0x0061,
- (Serif, true, false) => 0x1D41A,
- (Serif, false, true) => 0x1D44E,
- (Serif, true, true) => 0x1D482,
- (Sans, false, false) => 0x1D5BA,
- (Sans, true, false) => 0x1D5EE,
- (Sans, false, true) => 0x1D622,
- (Sans, true, true) => 0x1D656,
- (Cal, false, _) => 0x1D4B6,
- (Cal, true, _) => 0x1D4EA,
- (Frak, false, _) => 0x1D51E,
- (Frak, true, _) => 0x1D586,
- (Mono, _, _) => 0x1D68A,
- (Bb, _, _) => 0x1D552,
- },
-
- // Greek upper.
- 'Α'..='Ω' => match tuple {
- (Serif, false, false) => 0x0391,
- (Serif, true, false) => 0x1D6A8,
- (Serif, false, true) => 0x1D6E2,
- (Serif, true, true) => 0x1D71C,
- (Sans, _, false) => 0x1D756,
- (Sans, _, true) => 0x1D790,
- (Cal | Frak | Mono | Bb, _, _) => return c,
- },
-
- // Greek lower.
- 'α'..='ω' => match tuple {
- (Serif, false, false) => 0x03B1,
- (Serif, true, false) => 0x1D6C2,
- (Serif, false, true) => 0x1D6FC,
- (Serif, true, true) => 0x1D736,
- (Sans, _, false) => 0x1D770,
- (Sans, _, true) => 0x1D7AA,
- (Cal | Frak | Mono | Bb, _, _) => return c,
- },
-
- // Numbers.
- '0'..='9' => match tuple {
- (Serif, false, _) => 0x0030,
- (Serif, true, _) => 0x1D7CE,
- (Bb, _, _) => 0x1D7D8,
- (Sans, false, _) => 0x1D7E2,
- (Sans, true, _) => 0x1D7EC,
- (Mono, _, _) => 0x1D7F6,
- (Cal | Frak, _, _) => return c,
- },
-
- _ => unreachable!(),
- };
-
- std::char::from_u32(start + (c as u32 - base as u32)).unwrap()
-}
-
-fn basic_exception(c: char) -> Option<char> {
- Some(match c {
- '〈' => '⟨',
- '〉' => '⟩',
- '《' => '⟪',
- '》' => '⟫',
- _ => return None,
- })
-}
-
-fn latin_exception(
- c: char,
- variant: MathVariant,
- bold: bool,
- italic: bool,
-) -> Option<char> {
- use MathVariant::*;
- Some(match (c, variant, bold, italic) {
- ('B', Cal, false, _) => 'ℬ',
- ('E', Cal, false, _) => 'ℰ',
- ('F', Cal, false, _) => 'ℱ',
- ('H', Cal, false, _) => 'ℋ',
- ('I', Cal, false, _) => 'ℐ',
- ('L', Cal, false, _) => 'ℒ',
- ('M', Cal, false, _) => 'ℳ',
- ('R', Cal, false, _) => 'ℛ',
- ('C', Frak, false, _) => 'ℭ',
- ('H', Frak, false, _) => 'ℌ',
- ('I', Frak, false, _) => 'ℑ',
- ('R', Frak, false, _) => 'ℜ',
- ('Z', Frak, false, _) => 'ℨ',
- ('C', Bb, ..) => 'ℂ',
- ('H', Bb, ..) => 'ℍ',
- ('N', Bb, ..) => 'ℕ',
- ('P', Bb, ..) => 'ℙ',
- ('Q', Bb, ..) => 'ℚ',
- ('R', Bb, ..) => 'ℝ',
- ('Z', Bb, ..) => 'ℤ',
- ('h', Serif, false, true) => 'ℎ',
- ('e', Cal, false, _) => 'ℯ',
- ('g', Cal, false, _) => 'ℊ',
- ('o', Cal, false, _) => 'ℴ',
- ('ı', Serif, .., true) => '𝚤',
- ('ȷ', Serif, .., true) => '𝚥',
- _ => return None,
- })
-}
-
-fn greek_exception(
- c: char,
- variant: MathVariant,
- bold: bool,
- italic: bool,
-) -> Option<char> {
- use MathVariant::*;
- let list = match c {
- 'ϴ' => ['𝚹', '𝛳', '𝜭', '𝝧', '𝞡'],
- '∇' => ['𝛁', '𝛻', '𝜵', '𝝯', '𝞩'],
- '∂' => ['𝛛', '𝜕', '𝝏', '𝞉', '𝟃'],
- 'ϵ' => ['𝛜', '𝜖', '𝝐', '𝞊', '𝟄'],
- 'ϑ' => ['𝛝', '𝜗', '𝝑', '𝞋', '𝟅'],
- 'ϰ' => ['𝛞', '𝜘', '𝝒', '𝞌', '𝟆'],
- 'ϕ' => ['𝛟', '𝜙', '𝝓', '𝞍', '𝟇'],
- 'ϱ' => ['𝛠', '𝜚', '𝝔', '𝞎', '𝟈'],
- 'ϖ' => ['𝛡', '𝜛', '𝝕', '𝞏', '𝟉'],
- _ => return None,
- };
-
- Some(match (variant, bold, italic) {
- (Serif, true, false) => list[0],
- (Serif, false, true) => list[1],
- (Serif, true, true) => list[2],
- (Sans, _, false) => list[3],
- (Sans, _, true) => list[4],
- _ => return None,
- })
-}
diff --git a/library/src/math/underover.rs b/library/src/math/underover.rs
deleted file mode 100644
index 796c9ebc..00000000
--- a/library/src/math/underover.rs
+++ /dev/null
@@ -1,339 +0,0 @@
-use super::*;
-
-const BRACE_GAP: Em = Em::new(0.25);
-const BRACKET_GAP: Em = Em::new(0.25);
-
-/// A marker to distinguish under- vs. overlines.
-enum LineKind {
- Over,
- Under,
-}
-
-/// A horizontal line under content.
-///
-/// ## Example { #example }
-/// ```example
-/// $ underline(1 + 2 + ... + 5) $
-/// ```
-///
-/// Display: Underline
-/// Category: math
-#[element(LayoutMath)]
-pub struct UnderlineElem {
- /// The content above the line.
- #[required]
- pub body: Content,
-}
-
-impl LayoutMath for UnderlineElem {
- #[tracing::instrument(skip(ctx))]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- layout_underoverline(ctx, &self.body(), self.span(), LineKind::Under)
- }
-}
-
-/// A horizontal line over content.
-///
-/// ## Example { #example }
-/// ```example
-/// $ overline(1 + 2 + ... + 5) $
-/// ```
-///
-/// Display: Overline
-/// Category: math
-#[element(LayoutMath)]
-pub struct OverlineElem {
- /// The content below the line.
- #[required]
- pub body: Content,
-}
-
-impl LayoutMath for OverlineElem {
- #[tracing::instrument(skip(ctx))]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- layout_underoverline(ctx, &self.body(), self.span(), LineKind::Over)
- }
-}
-
-/// layout under- or overlined content
-fn layout_underoverline(
- ctx: &mut MathContext,
- body: &Content,
- span: Span,
- line: LineKind,
-) -> SourceResult<()> {
- let (extra_height, content, line_pos, content_pos, baseline, bar_height);
- match line {
- LineKind::Under => {
- let sep = scaled!(ctx, underbar_extra_descender);
- bar_height = scaled!(ctx, underbar_rule_thickness);
- let gap = scaled!(ctx, underbar_vertical_gap);
- extra_height = sep + bar_height + gap;
-
- content = ctx.layout_fragment(body)?;
-
- line_pos = Point::with_y(content.height() + gap + bar_height / 2.0);
- content_pos = Point::zero();
- baseline = content.ascent()
- }
- LineKind::Over => {
- let sep = scaled!(ctx, overbar_extra_ascender);
- bar_height = scaled!(ctx, overbar_rule_thickness);
- let gap = scaled!(ctx, overbar_vertical_gap);
- extra_height = sep + bar_height + gap;
-
- ctx.style(ctx.style.with_cramped(true));
- content = ctx.layout_fragment(body)?;
- ctx.unstyle();
-
- line_pos = Point::with_y(sep + bar_height / 2.0);
- content_pos = Point::with_y(extra_height);
- baseline = content.ascent() + extra_height;
- }
- }
-
- let width = content.width();
- let height = content.height() + extra_height;
- let size = Size::new(width, height);
-
- let content_class = content.class().unwrap_or(MathClass::Normal);
- let mut frame = Frame::new(size);
- frame.set_baseline(baseline);
- frame.push_frame(content_pos, content.into_frame());
- frame.push(
- line_pos,
- FrameItem::Shape(
- Geometry::Line(Point::with_x(width)).stroked(Stroke {
- paint: TextElem::fill_in(ctx.styles()),
- thickness: bar_height,
- ..Stroke::default()
- }),
- span,
- ),
- );
-
- ctx.push(FrameFragment::new(ctx, frame).with_class(content_class));
-
- Ok(())
-}
-
-/// A horizontal brace under content, with an optional annotation below.
-///
-/// ## Example { #example }
-/// ```example
-/// $ underbrace(1 + 2 + ... + 5, "numbers") $
-/// ```
-///
-/// Display: Underbrace
-/// Category: math
-#[element(LayoutMath)]
-pub struct UnderbraceElem {
- /// The content above the brace.
- #[required]
- pub body: Content,
-
- /// The optional content below the brace.
- #[positional]
- pub annotation: Option<Content>,
-}
-
-impl LayoutMath for UnderbraceElem {
- #[tracing::instrument(skip(ctx))]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- layout_underoverspreader(
- ctx,
- &self.body(),
- &self.annotation(ctx.styles()),
- '⏟',
- BRACE_GAP,
- false,
- self.span(),
- )
- }
-}
-
-/// A horizontal brace over content, with an optional annotation above.
-///
-/// ## Example { #example }
-/// ```example
-/// $ overbrace(1 + 2 + ... + 5, "numbers") $
-/// ```
-///
-/// Display: Overbrace
-/// Category: math
-#[element(LayoutMath)]
-pub struct OverbraceElem {
- /// The content below the brace.
- #[required]
- pub body: Content,
-
- /// The optional content above the brace.
- #[positional]
- pub annotation: Option<Content>,
-}
-
-impl LayoutMath for OverbraceElem {
- #[tracing::instrument(skip(ctx))]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- layout_underoverspreader(
- ctx,
- &self.body(),
- &self.annotation(ctx.styles()),
- '⏞',
- BRACE_GAP,
- true,
- self.span(),
- )
- }
-}
-
-/// A horizontal bracket under content, with an optional annotation below.
-///
-/// ## Example { #example }
-/// ```example
-/// $ underbracket(1 + 2 + ... + 5, "numbers") $
-/// ```
-///
-/// Display: Underbracket
-/// Category: math
-#[element(LayoutMath)]
-pub struct UnderbracketElem {
- /// The content above the bracket.
- #[required]
- pub body: Content,
-
- /// The optional content below the bracket.
- #[positional]
- pub annotation: Option<Content>,
-}
-
-impl LayoutMath for UnderbracketElem {
- #[tracing::instrument(skip(ctx))]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- layout_underoverspreader(
- ctx,
- &self.body(),
- &self.annotation(ctx.styles()),
- '⎵',
- BRACKET_GAP,
- false,
- self.span(),
- )
- }
-}
-
-/// A horizontal bracket over content, with an optional annotation above.
-///
-/// ## Example { #example }
-/// ```example
-/// $ overbracket(1 + 2 + ... + 5, "numbers") $
-/// ```
-///
-/// Display: Overbracket
-/// Category: math
-#[element(LayoutMath)]
-pub struct OverbracketElem {
- /// The content below the bracket.
- #[required]
- pub body: Content,
-
- /// The optional content above the bracket.
- #[positional]
- pub annotation: Option<Content>,
-}
-
-impl LayoutMath for OverbracketElem {
- #[tracing::instrument(skip(ctx))]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- layout_underoverspreader(
- ctx,
- &self.body(),
- &self.annotation(ctx.styles()),
- '⎴',
- BRACKET_GAP,
- true,
- self.span(),
- )
- }
-}
-
-/// Layout an over- or underbrace-like object.
-fn layout_underoverspreader(
- ctx: &mut MathContext,
- body: &Content,
- annotation: &Option<Content>,
- c: char,
- gap: Em,
- reverse: bool,
- span: Span,
-) -> SourceResult<()> {
- let gap = gap.scaled(ctx);
- let body = ctx.layout_row(body)?;
- let body_class = body.class();
- let body = body.into_fragment(ctx);
- let glyph = GlyphFragment::new(ctx, c, span);
- let stretched = glyph.stretch_horizontal(ctx, body.width(), Abs::zero());
-
- let mut rows = vec![MathRow::new(vec![body]), stretched.into()];
- ctx.style(if reverse {
- ctx.style.for_subscript()
- } else {
- ctx.style.for_superscript()
- });
- rows.extend(
- annotation
- .as_ref()
- .map(|annotation| ctx.layout_row(annotation))
- .transpose()?,
- );
- ctx.unstyle();
-
- let mut baseline = 0;
- if reverse {
- rows.reverse();
- baseline = rows.len() - 1;
- }
-
- let frame = stack(ctx, rows, Align::Center, gap, baseline);
- ctx.push(FrameFragment::new(ctx, frame).with_class(body_class));
-
- Ok(())
-}
-
-/// Stack rows on top of each other.
-///
-/// Add a `gap` between each row and uses the baseline of the `baseline`th
-/// row for the whole frame.
-pub(super) fn stack(
- ctx: &MathContext,
- rows: Vec<MathRow>,
- align: Align,
- gap: Abs,
- baseline: usize,
-) -> Frame {
- let rows: Vec<_> = rows.into_iter().flat_map(|r| r.rows()).collect();
- let AlignmentResult { points, width } = alignments(&rows);
- let rows: Vec<_> = rows
- .into_iter()
- .map(|row| row.into_aligned_frame(ctx, &points, align))
- .collect();
-
- let mut y = Abs::zero();
- let mut frame = Frame::new(Size::new(
- width,
- rows.iter().map(|row| row.height()).sum::<Abs>()
- + rows.len().saturating_sub(1) as f64 * gap,
- ));
-
- for (i, row) in rows.into_iter().enumerate() {
- let x = align.position(width - row.width());
- let pos = Point::new(x, y);
- if i == baseline {
- frame.set_baseline(y + row.baseline());
- }
- y += row.height() + gap;
- frame.push_frame(pos, row);
- }
-
- frame
-}