summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-01-26 10:50:33 +0100
committerGitHub <noreply@github.com>2024-01-26 09:50:33 +0000
commitb09d6ae31c78463ad741644194c17ddd47c2bd56 (patch)
tree99363b95467daa991a2aedf8727d5e02dd275fb5
parentd8464a9a8174ac315ba4bff2e26b7d6abbcb9de6 (diff)
Refactor math styling to bring it closer to normal styling (#3262)
-rw-r--r--crates/typst/src/foundations/styles.rs50
-rw-r--r--crates/typst/src/math/accent.rs30
-rw-r--r--crates/typst/src/math/align.rs4
-rw-r--r--crates/typst/src/math/attach.rs119
-rw-r--r--crates/typst/src/math/cancel.rs14
-rw-r--r--crates/typst/src/math/class.rs18
-rw-r--r--crates/typst/src/math/ctx.rs244
-rw-r--r--crates/typst/src/math/equation.rs48
-rw-r--r--crates/typst/src/math/frac.rs72
-rw-r--r--crates/typst/src/math/fragment.rs180
-rw-r--r--crates/typst/src/math/lr.rs35
-rw-r--r--crates/typst/src/math/matrix.rs72
-rw-r--r--crates/typst/src/math/mod.rs61
-rw-r--r--crates/typst/src/math/op.rs14
-rw-r--r--crates/typst/src/math/root.rs40
-rw-r--r--crates/typst/src/math/row.rs38
-rw-r--r--crates/typst/src/math/spacing.rs5
-rw-r--r--crates/typst/src/math/stretch.rs18
-rw-r--r--crates/typst/src/math/style.rs289
-rw-r--r--crates/typst/src/math/underover.rs96
-rw-r--r--tests/ref/math/class.pngbin7599 -> 7545 bytes
-rw-r--r--tests/ref/math/equation-show.pngbin0 -> 2428 bytes
-rw-r--r--tests/typ/math/equation-show.typ7
23 files changed, 699 insertions, 755 deletions
diff --git a/crates/typst/src/foundations/styles.rs b/crates/typst/src/foundations/styles.rs
index da8447c3..f44a320f 100644
--- a/crates/typst/src/foundations/styles.rs
+++ b/crates/typst/src/foundations/styles.rs
@@ -445,17 +445,16 @@ impl<'a> StyleChain<'a> {
Self { head: &root.0, tail: None }
}
- /// Make the given style list the first link of this chain.
+ /// Make the given chainable the first link of this chain.
///
/// The resulting style chain contains styles from `local` as well as
/// `self`. The ones from `local` take precedence over the ones from
/// `self`. For folded properties `local` contributes the inner value.
- pub fn chain<'b>(&'b self, local: &'b Styles) -> StyleChain<'b> {
- if local.is_empty() {
- *self
- } else {
- StyleChain { head: &local.0, tail: Some(self) }
- }
+ pub fn chain<'b, C>(&'b self, local: &'b C) -> StyleChain<'b>
+ where
+ C: Chainable,
+ {
+ Chainable::chain(local, self)
}
/// Cast the first value for the given property in the chain,
@@ -630,6 +629,43 @@ impl PartialEq for StyleChain<'_> {
}
}
+/// Things that can be attached to a style chain.
+pub trait Chainable {
+ /// Attach `self` as the first link of the chain.
+ fn chain<'a>(&'a self, outer: &'a StyleChain<'_>) -> StyleChain<'a>;
+}
+
+impl Chainable for Prehashed<Style> {
+ fn chain<'a>(&'a self, outer: &'a StyleChain<'_>) -> StyleChain<'a> {
+ StyleChain {
+ head: std::slice::from_ref(self),
+ tail: Some(outer),
+ }
+ }
+}
+
+impl Chainable for [Prehashed<Style>] {
+ fn chain<'a>(&'a self, outer: &'a StyleChain<'_>) -> StyleChain<'a> {
+ if self.is_empty() {
+ *outer
+ } else {
+ StyleChain { head: self, tail: Some(outer) }
+ }
+ }
+}
+
+impl<const N: usize> Chainable for [Prehashed<Style>; N] {
+ fn chain<'a>(&'a self, outer: &'a StyleChain<'_>) -> StyleChain<'a> {
+ Chainable::chain(self.as_slice(), outer)
+ }
+}
+
+impl Chainable for Styles {
+ fn chain<'a>(&'a self, outer: &'a StyleChain<'_>) -> StyleChain<'a> {
+ Chainable::chain(self.0.as_slice(), outer)
+ }
+}
+
/// An iterator over the entries in a style chain.
struct Entries<'a> {
inner: std::slice::Iter<'a, Prehashed<Style>>,
diff --git a/crates/typst/src/math/accent.rs b/crates/typst/src/math/accent.rs
index d325596c..daf5dffc 100644
--- a/crates/typst/src/math/accent.rs
+++ b/crates/typst/src/math/accent.rs
@@ -1,10 +1,11 @@
-use unicode_math_class::MathClass;
-
use crate::diag::{bail, SourceResult};
-use crate::foundations::{cast, elem, Content, Packed, Resolve, Smart, Value};
+use crate::foundations::{
+ cast, elem, Content, Packed, Resolve, Smart, StyleChain, Value,
+};
use crate::layout::{Em, Frame, Length, Point, Rel, Size};
use crate::math::{
- FrameFragment, GlyphFragment, LayoutMath, MathContext, MathFragment, Scaled,
+ style_cramped, FrameFragment, GlyphFragment, LayoutMath, MathContext, MathFragment,
+ Scaled,
};
use crate::symbols::Symbol;
use crate::text::TextElem;
@@ -64,26 +65,25 @@ pub struct AccentElem {
impl LayoutMath for Packed<AccentElem> {
#[typst_macros::time(name = "math.accent", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- ctx.style(ctx.style.with_cramped(true));
- let base = ctx.layout_fragment(self.base())?;
- ctx.unstyle();
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
+ let cramped = style_cramped();
+ let base = ctx.layout_fragment(self.base(), styles.chain(&cramped))?;
// Preserve class to preserve automatic spacing.
- let base_class = base.class().unwrap_or(MathClass::Normal);
+ let base_class = base.class();
let base_attach = base.accent_attach();
let width = self
- .size(ctx.styles())
+ .size(styles)
.unwrap_or(Rel::one())
- .resolve(ctx.styles())
+ .resolve(styles)
.relative_to(base.width());
// 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 glyph = GlyphFragment::new(ctx, styles, *c, self.span());
+ let short_fall = ACCENT_SHORT_FALL.at(glyph.font_size);
let variant = glyph.stretch_horizontal(ctx, width, short_fall);
let accent = variant.frame;
let accent_attach = variant.accent_attach;
@@ -92,7 +92,7 @@ impl LayoutMath for Packed<AccentElem> {
// 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 accent_base_height = scaled!(ctx, styles, 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);
@@ -111,7 +111,7 @@ impl LayoutMath for Packed<AccentElem> {
frame.push_frame(accent_pos, accent);
frame.push_frame(base_pos, base.into_frame());
ctx.push(
- FrameFragment::new(ctx, frame)
+ FrameFragment::new(ctx, styles, frame)
.with_class(base_class)
.with_base_ascent(base_ascent)
.with_italics_correction(base_italics_correction)
diff --git a/crates/typst/src/math/align.rs b/crates/typst/src/math/align.rs
index a1d396da..e7d0fab1 100644
--- a/crates/typst/src/math/align.rs
+++ b/crates/typst/src/math/align.rs
@@ -1,5 +1,5 @@
use crate::diag::SourceResult;
-use crate::foundations::{elem, Packed};
+use crate::foundations::{elem, Packed, StyleChain};
use crate::layout::Abs;
use crate::math::{LayoutMath, MathContext, MathFragment, MathRow};
@@ -8,7 +8,7 @@ use crate::math::{LayoutMath, MathContext, MathFragment, MathRow};
pub struct AlignPointElem {}
impl LayoutMath for Packed<AlignPointElem> {
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
+ fn layout_math(&self, ctx: &mut MathContext, _: StyleChain) -> SourceResult<()> {
ctx.push(MathFragment::Align);
Ok(())
}
diff --git a/crates/typst/src/math/attach.rs b/crates/typst/src/math/attach.rs
index 09541d1c..d7b3acee 100644
--- a/crates/typst/src/math/attach.rs
+++ b/crates/typst/src/math/attach.rs
@@ -4,7 +4,8 @@ use crate::diag::SourceResult;
use crate::foundations::{elem, Content, Packed, StyleChain};
use crate::layout::{Abs, Frame, Point, Size};
use crate::math::{
- FrameFragment, LayoutMath, MathContext, MathFragment, MathSize, Scaled,
+ style_for_subscript, style_for_superscript, EquationElem, FrameFragment, LayoutMath,
+ MathContext, MathFragment, MathSize, Scaled,
};
use crate::text::TextElem;
@@ -50,32 +51,32 @@ pub struct AttachElem {
impl LayoutMath for Packed<AttachElem> {
#[typst_macros::time(name = "math.attach", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> 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())?;
+ let layout_attachment =
+ |ctx: &mut MathContext, styles: StyleChain, getter: GetAttachment| {
+ getter(self, styles)
+ .map(|elem| ctx.layout_fragment(&elem, styles))
+ .transpose()
+ };
+
+ let base = ctx.layout_fragment(self.base(), styles)?;
- ctx.style(ctx.style.for_superscript());
- let tl = layout_attachment(ctx, AttachElem::tl)?;
- let tr = layout_attachment(ctx, AttachElem::tr)?;
- let t = layout_attachment(ctx, AttachElem::t)?;
- ctx.unstyle();
+ let sup_style = style_for_superscript(styles);
+ let tl = layout_attachment(ctx, styles.chain(&sup_style), AttachElem::tl)?;
+ let tr = layout_attachment(ctx, styles.chain(&sup_style), AttachElem::tr)?;
+ let t = layout_attachment(ctx, styles.chain(&sup_style), AttachElem::t)?;
- ctx.style(ctx.style.for_subscript());
- let bl = layout_attachment(ctx, AttachElem::bl)?;
- let br = layout_attachment(ctx, AttachElem::br)?;
- let b = layout_attachment(ctx, AttachElem::b)?;
- ctx.unstyle();
+ let sub_style = style_for_subscript(styles);
+ let bl = layout_attachment(ctx, styles.chain(&sub_style), AttachElem::bl)?;
+ let br = layout_attachment(ctx, styles.chain(&sub_style), AttachElem::br)?;
+ let b = layout_attachment(ctx, styles.chain(&sub_style), AttachElem::b)?;
- let limits = base.limits().active(ctx);
+ let limits = base.limits().active(styles);
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])
+ layout_attachments(ctx, styles, base, [tl, t, tr, bl, b, br])
}
}
@@ -98,21 +99,23 @@ pub struct PrimesElem {
impl LayoutMath for Packed<PrimesElem> {
#[typst_macros::time(name = "math.primes", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
match *self.count() {
count @ 1..=4 => {
- let f = ctx.layout_fragment(&TextElem::packed(match count {
+ let c = match count {
1 => '′',
2 => '″',
3 => '‴',
4 => '⁗',
_ => unreachable!(),
- }))?;
+ };
+ let f = ctx.layout_fragment(&TextElem::packed(c), styles)?;
ctx.push(f);
}
count => {
// Custom amount of primes
- let prime = ctx.layout_fragment(&TextElem::packed('′'))?.into_frame();
+ let prime =
+ ctx.layout_fragment(&TextElem::packed('′'), styles)?.into_frame();
let width = prime.width() * (count + 1) as f64 / 2.0;
let mut frame = Frame::soft(Size::new(width, prime.height()));
frame.set_baseline(prime.ascent());
@@ -123,7 +126,7 @@ impl LayoutMath for Packed<PrimesElem> {
prime.clone(),
)
}
- ctx.push(FrameFragment::new(ctx, frame));
+ ctx.push(FrameFragment::new(ctx, styles, frame));
}
}
Ok(())
@@ -144,8 +147,8 @@ pub struct ScriptsElem {
impl LayoutMath for Packed<ScriptsElem> {
#[typst_macros::time(name = "math.scripts", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- let mut fragment = ctx.layout_fragment(self.body())?;
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
+ let mut fragment = ctx.layout_fragment(self.body(), styles)?;
fragment.set_limits(Limits::Never);
ctx.push(fragment);
Ok(())
@@ -173,13 +176,10 @@ pub struct LimitsElem {
impl LayoutMath for Packed<LimitsElem> {
#[typst_macros::time(name = "math.limits", span = self.span())]
- 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
- });
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
+ let limits = if self.inline(styles) { Limits::Always } else { Limits::Display };
+ let mut fragment = ctx.layout_fragment(self.body(), styles)?;
+ fragment.set_limits(limits);
ctx.push(fragment);
Ok(())
}
@@ -222,10 +222,10 @@ impl Limits {
}
/// Whether limits should be displayed in this context
- pub fn active(&self, ctx: &MathContext) -> bool {
+ pub fn active(&self, styles: StyleChain) -> bool {
match self {
Self::Always => true,
- Self::Display => ctx.style.size == MathSize::Display,
+ Self::Display => EquationElem::size_in(styles) == MathSize::Display,
Self::Never => false,
}
}
@@ -240,17 +240,18 @@ macro_rules! measure {
/// Layout the attachments.
fn layout_attachments(
ctx: &mut MathContext,
+ styles: StyleChain,
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]);
+ compute_shifts_up_and_down(ctx, styles, &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 base_class = base.class();
let mut ascent = base_ascent
.max(shift_up + measure!(tr, ascent))
@@ -269,9 +270,9 @@ fn layout_attachments(
let post_width_max =
(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 (center_frame, base_offset) = attach_top_and_bottom(ctx, styles, base, t, b);
if [&tl, &bl, &tr, &br].iter().all(|&e| e.is_none()) {
- ctx.push(FrameFragment::new(ctx, center_frame).with_class(base_class));
+ ctx.push(FrameFragment::new(ctx, styles, center_frame).with_class(base_class));
return Ok(());
}
@@ -279,7 +280,10 @@ fn layout_attachments(
descent.set_max(center_frame.descent());
let mut frame = Frame::soft(Size::new(
- pre_width_max + base_width + post_width_max + scaled!(ctx, space_after_script),
+ pre_width_max
+ + base_width
+ + post_width_max
+ + scaled!(ctx, styles, space_after_script),
ascent + descent,
));
frame.set_baseline(ascent);
@@ -316,21 +320,22 @@ fn layout_attachments(
frame.push_frame(pos, br.into_frame());
}
- ctx.push(FrameFragment::new(ctx, frame).with_class(base_class));
+ ctx.push(FrameFragment::new(ctx, styles, frame).with_class(base_class));
Ok(())
}
fn attach_top_and_bottom(
ctx: &mut MathContext,
+ styles: StyleChain,
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 upper_gap_min = scaled!(ctx, styles, upper_limit_gap_min);
+ let upper_rise_min = scaled!(ctx, styles, upper_limit_baseline_rise_min);
+ let lower_gap_min = scaled!(ctx, styles, lower_limit_gap_min);
+ let lower_drop_min = scaled!(ctx, styles, lower_limit_baseline_drop_min);
let mut base_offset = Abs::zero();
let mut width = base.width();
@@ -372,22 +377,24 @@ fn attach_top_and_bottom(
fn compute_shifts_up_and_down(
ctx: &MathContext,
+ styles: StyleChain,
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)
+ let sup_shift_up = if EquationElem::cramped_in(styles) {
+ scaled!(ctx, styles, superscript_shift_up_cramped)
} else {
- scaled!(ctx, superscript_shift_up)
+ scaled!(ctx, styles, 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 sup_bottom_min = scaled!(ctx, styles, superscript_bottom_min);
+ let sup_bottom_max_with_sub =
+ scaled!(ctx, styles, superscript_bottom_max_with_subscript);
+ let sup_drop_max = scaled!(ctx, styles, superscript_baseline_drop_max);
+ let gap_min = scaled!(ctx, styles, sub_superscript_gap_min);
+ let sub_shift_down = scaled!(ctx, styles, subscript_shift_down);
+ let sub_top_max = scaled!(ctx, styles, subscript_top_max);
+ let sub_drop_min = scaled!(ctx, styles, subscript_baseline_drop_min);
let mut shift_up = Abs::zero();
let mut shift_down = Abs::zero();
diff --git a/crates/typst/src/math/cancel.rs b/crates/typst/src/math/cancel.rs
index e01b98ca..039ef752 100644
--- a/crates/typst/src/math/cancel.rs
+++ b/crates/typst/src/math/cancel.rs
@@ -1,7 +1,5 @@
-use unicode_math_class::MathClass;
-
use crate::diag::{At, SourceResult};
-use crate::foundations::{cast, elem, Content, Func, Packed, Resolve, Smart};
+use crate::foundations::{cast, elem, Content, Func, Packed, Resolve, Smart, StyleChain};
use crate::layout::{
Abs, Angle, Frame, FrameItem, Length, Point, Ratio, Rel, Size, Transform,
};
@@ -107,17 +105,15 @@ pub struct CancelElem {
impl LayoutMath for Packed<CancelElem> {
#[typst_macros::time(name = "math.cancel", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- let body = ctx.layout_fragment(self.body())?;
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
+ let body = ctx.layout_fragment(self.body(), styles)?;
// Preserve properties of body.
- let body_class = body.class().unwrap_or(MathClass::Special);
+ let body_class = body.class();
let body_italics = body.italics_correction();
let body_attach = body.accent_attach();
let body_text_like = body.is_text_like();
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);
@@ -155,7 +151,7 @@ impl LayoutMath for Packed<CancelElem> {
}
ctx.push(
- FrameFragment::new(ctx, body)
+ FrameFragment::new(ctx, styles, body)
.with_class(body_class)
.with_italics_correction(body_italics)
.with_accent_attach(body_attach)
diff --git a/crates/typst/src/math/class.rs b/crates/typst/src/math/class.rs
index 1b6c638c..50aabede 100644
--- a/crates/typst/src/math/class.rs
+++ b/crates/typst/src/math/class.rs
@@ -1,8 +1,9 @@
+use comemo::Prehashed;
use unicode_math_class::MathClass;
use crate::diag::SourceResult;
-use crate::foundations::{elem, Content, Packed};
-use crate::math::{LayoutMath, Limits, MathContext};
+use crate::foundations::{elem, Content, Packed, StyleChain};
+use crate::math::{EquationElem, LayoutMath, Limits, MathContext};
/// Forced use of a certain math class.
///
@@ -34,13 +35,12 @@ pub struct ClassElem {
impl LayoutMath for Packed<ClassElem> {
#[typst_macros::time(name = "math.class", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- ctx.style(ctx.style.with_class(*self.class()));
- let mut fragment = ctx.layout_fragment(self.body())?;
- ctx.unstyle();
-
- fragment.set_class(*self.class());
- fragment.set_limits(Limits::for_class(*self.class()));
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
+ let class = *self.class();
+ let style = Prehashed::new(EquationElem::set_class(Some(class)));
+ let mut fragment = ctx.layout_fragment(self.body(), styles.chain(&style))?;
+ fragment.set_class(class);
+ fragment.set_limits(Limits::for_class(class));
ctx.push(fragment);
Ok(())
}
diff --git a/crates/typst/src/math/ctx.rs b/crates/typst/src/math/ctx.rs
index 5842aae0..5b4227b4 100644
--- a/crates/typst/src/math/ctx.rs
+++ b/crates/typst/src/math/ctx.rs
@@ -12,29 +12,30 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::diag::SourceResult;
use crate::engine::Engine;
-use crate::foundations::{Content, Packed, Smart, StyleChain, Styles};
+use crate::foundations::{Content, Packed, Smart, StyleChain};
use crate::layout::{Abs, Axes, BoxElem, Em, Frame, LayoutMultiple, Regions, Size};
use crate::math::{
- FrameFragment, GlyphFragment, LayoutMath, MathFragment, MathRow, MathSize, MathStyle,
- MathVariant, THICK,
+ scaled_font_size, styled_char, EquationElem, FrameFragment, GlyphFragment,
+ LayoutMath, MathFragment, MathRow, MathSize, THICK,
};
use crate::model::ParElem;
-use crate::realize::realize;
use crate::syntax::{is_newline, Span};
use crate::text::{
- features, variant, BottomEdge, BottomEdgeMetric, Font, FontStyle, FontWeight,
- TextElem, TextSize, TopEdge, TopEdgeMetric,
+ features, BottomEdge, BottomEdgeMetric, Font, TextElem, TextSize, TopEdge,
+ TopEdgeMetric,
};
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, $styles:expr, text: $text:ident, display: $display:ident $(,)?) => {
+ match $crate::math::EquationElem::size_in($styles) {
+ $crate::math::MathSize::Display => scaled!($ctx, $styles, $display),
+ _ => scaled!($ctx, $styles, $text),
}
};
- ($ctx:expr, $name:ident) => {
- $ctx.constants.$name().scaled($ctx)
+ ($ctx:expr, $styles:expr, $name:ident) => {
+ $ctx.constants
+ .$name()
+ .scaled($ctx, $crate::math::scaled_font_size($ctx, $styles))
};
}
@@ -46,8 +47,10 @@ macro_rules! percent {
/// The context for math layout.
pub struct MathContext<'a, 'b, 'v> {
+ // External.
pub engine: &'v mut Engine<'b>,
pub regions: Regions<'static>,
+ // Font-related.
pub font: &'a Font,
pub ttf: &'a ttf_parser::Face<'a>,
pub table: ttf_parser::math::Table<'a>,
@@ -55,12 +58,8 @@ pub struct MathContext<'a, 'b, 'v> {
pub ssty_table: Option<ttf_parser::gsub::AlternateSubstitution<'a>>,
pub glyphwise_tables: Option<Vec<GlyphwiseSubsts<'a>>>,
pub space_width: Em,
+ // Mutable.
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> {
@@ -69,7 +68,6 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
styles: StyleChain<'a>,
regions: Regions,
font: &'a Font,
- block: bool,
) -> Self {
let math_table = font.ttf().tables().math.unwrap();
let gsub_table = font.ttf().tables().gsub;
@@ -96,7 +94,6 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
.collect()
});
- let size = TextElem::size_in(styles);
let ttf = font.ttf();
let space_width = ttf
.glyph_index(' ')
@@ -104,7 +101,6 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
.map(|advance| font.to_em(advance))
.unwrap_or(THICK);
- let variant = variant(styles);
Self {
engine,
regions: Regions::one(regions.base(), Axes::splat(false)),
@@ -116,21 +112,6 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
glyphwise_tables,
space_width,
fragments: vec![],
- local: Styles::new(),
- style: MathStyle {
- variant: MathVariant::Serif,
- size: if block { MathSize::Display } else { MathSize::Text },
- class: Smart::Auto,
- 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![],
}
}
@@ -142,59 +123,92 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
self.fragments.extend(fragments);
}
- pub fn layout_root(&mut self, elem: &dyn LayoutMath) -> SourceResult<MathRow> {
- let row = self.layout_fragments(elem)?;
+ pub fn layout_root(
+ &mut self,
+ elem: &dyn LayoutMath,
+ styles: StyleChain,
+ ) -> SourceResult<MathRow> {
+ let row = self.layout_fragments(elem, styles)?;
Ok(MathRow::new(row))
}
pub fn layout_fragment(
&mut self,
elem: &dyn LayoutMath,
+ styles: StyleChain,
) -> SourceResult<MathFragment> {
- let row = self.layout_fragments(elem)?;
- Ok(MathRow::new(row).into_fragment(self))
+ let row = self.layout_fragments(elem, styles)?;
+ Ok(MathRow::new(row).into_fragment(self, styles))
}
pub fn layout_fragments(
&mut self,
elem: &dyn LayoutMath,
+ styles: StyleChain,
) -> SourceResult<Vec<MathFragment>> {
let prev = std::mem::take(&mut self.fragments);
- elem.layout_math(self)?;
+ elem.layout_math(self, styles)?;
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)?;
+ pub fn layout_row(
+ &mut self,
+ elem: &dyn LayoutMath,
+ styles: StyleChain,
+ ) -> SourceResult<MathRow> {
+ let fragments = self.layout_fragments(elem, styles)?;
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_frame(
+ &mut self,
+ elem: &dyn LayoutMath,
+ styles: StyleChain,
+ ) -> SourceResult<Frame> {
+ Ok(self.layout_fragment(elem, styles)?.into_frame())
}
- pub fn layout_box(&mut self, boxed: &Packed<BoxElem>) -> SourceResult<Frame> {
- boxed.layout(self.engine, self.outer.chain(&self.local), self.regions)
+ pub fn layout_box(
+ &mut self,
+ boxed: &Packed<BoxElem>,
+ styles: StyleChain,
+ ) -> SourceResult<Frame> {
+ let local = Prehashed::new(TextElem::set_size(TextSize(
+ scaled_font_size(self, styles).into(),
+ )));
+ boxed.layout(self.engine, styles.chain(&local), self.regions)
}
- pub fn layout_content(&mut self, content: &Content) -> SourceResult<Frame> {
+ pub fn layout_content(
+ &mut self,
+ content: &Content,
+ styles: StyleChain,
+ ) -> SourceResult<Frame> {
+ let local = Prehashed::new(TextElem::set_size(TextSize(
+ scaled_font_size(self, styles).into(),
+ )));
Ok(content
- .layout(self.engine, self.outer.chain(&self.local), self.regions)?
+ .layout(self.engine, styles.chain(&local), self.regions)?
.into_frame())
}
- pub fn layout_text(&mut self, elem: &Packed<TextElem>) -> SourceResult<MathFragment> {
+ pub fn layout_text(
+ &mut self,
+ elem: &Packed<TextElem>,
+ styles: StyleChain,
+ ) -> SourceResult<MathFragment> {
let text = elem.text();
let span = elem.span();
let mut chars = text.chars();
+ let math_size = EquationElem::size_in(styles);
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))
+ .map(|c| styled_char(styles, c))
+ .and_then(|c| GlyphFragment::try_new(self, styles, c, span))
{
// A single letter that is available in the math font.
- match self.style.size {
+ match math_size {
MathSize::Script => {
glyph.make_scriptsize(self);
}
@@ -204,10 +218,9 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
_ => (),
}
- let class = self.style.class.as_custom().or(glyph.class);
- if class == Some(MathClass::Large) {
- let mut variant = if self.style.size == MathSize::Display {
- let height = scaled!(self, display_operator_min_height)
+ if glyph.class == MathClass::Large {
+ let mut variant = if math_size == MathSize::Display {
+ let height = scaled!(self, styles, display_operator_min_height)
.max(SQRT_2 * glyph.height());
glyph.stretch_vertical(self, height, Abs::zero())
} else {
@@ -223,18 +236,23 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
// 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 c = styled_char(styles, c);
+ fragments.push(GlyphFragment::new(self, styles, c, span).into());
}
- let frame = MathRow::new(fragments).into_frame(self);
- FrameFragment::new(self, frame).with_text_like(true).into()
+ let frame = MathRow::new(fragments).into_frame(self, styles);
+ FrameFragment::new(self, styles, frame).with_text_like(true).into()
} else {
+ let local = [
+ TextElem::set_top_edge(TopEdge::Metric(TopEdgeMetric::Bounds)),
+ TextElem::set_bottom_edge(BottomEdge::Metric(BottomEdgeMetric::Bounds)),
+ TextElem::set_size(TextSize(scaled_font_size(self, styles).into())),
+ EquationElem::set_italic(Smart::Custom(false)),
+ ]
+ .map(Prehashed::new);
+
// Anything else is handled by Typst's standard text layout.
- 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 styles = styles.chain(&local);
+ let text: EcoString = text.chars().map(|c| styled_char(styles, c)).collect();
if text.contains(is_newline) {
let mut fragments = vec![];
for (i, piece) in text.split(is_newline).enumerate() {
@@ -242,117 +260,65 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
fragments.push(MathFragment::Linebreak);
}
if !piece.is_empty() {
- fragments.push(self.layout_complex_text(piece, span)?.into());
+ fragments
+ .push(self.layout_complex_text(piece, span, styles)?.into());
}
}
- let mut frame = MathRow::new(fragments).into_frame(self);
- let axis = scaled!(self, axis_height);
+ let mut frame = MathRow::new(fragments).into_frame(self, styles);
+ let axis = scaled!(self, styles, axis_height);
frame.set_baseline(frame.height() / 2.0 + axis);
- FrameFragment::new(self, frame).into()
+ FrameFragment::new(self, styles, frame).into()
} else {
- self.layout_complex_text(&text, span)?.into()
+ self.layout_complex_text(&text, span, styles)?.into()
}
};
Ok(fragment)
}
- pub fn layout_complex_text(
+ fn layout_complex_text(
&mut self,
text: &str,
span: Span,
+ styles: StyleChain,
) -> SourceResult<FrameFragment> {
- let spaced = text.graphemes(true).nth(1).is_some();
- let elem = TextElem::packed(text)
- .styled(TextElem::set_top_edge(TopEdge::Metric(TopEdgeMetric::Bounds)))
- .styled(TextElem::set_bottom_edge(BottomEdge::Metric(
- BottomEdgeMetric::Bounds,
- )))
- .spanned(span);
-
// There isn't a natural width for a paragraph in a math environment;
// because it will be placed somewhere probably not at the left margin
- // it will overflow. So emulate an `hbox` instead and allow the paragraph
+ // it will overflow. So emulate an `hbox` instead and allow the paragraph
// to extend as far as needed.
- let span = elem.span();
- let frame = Packed::new(ParElem::new(vec![Prehashed::new(elem)]))
+ let spaced = text.graphemes(true).nth(1).is_some();
+ let text = TextElem::packed(text).spanned(span);
+ let par = ParElem::new(vec![Prehashed::new(text)]);
+ let frame = Packed::new(par)
.spanned(span)
- .layout(
- self.engine,
- self.outer.chain(&self.local),
- false,
- Size::splat(Abs::inf()),
- false,
- )?
+ .layout(self.engine, styles, false, Size::splat(Abs::inf()), false)?
.into_frame();
- Ok(FrameFragment::new(self, frame)
+ Ok(FrameFragment::new(self, styles, frame)
.with_class(MathClass::Alphabetic)
.with_text_like(true)
.with_spaced(spaced))
}
-
- pub fn styles(&self) -> StyleChain {
- self.outer.chain(&self.local)
- }
-
- pub fn realize(&mut self, content: &Content) -> SourceResult<Option<Content>> {
- realize(self.engine, 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 {
- // The normal weight is what we started with.
- // It's 400 for CM Regular, 450 for CM Book.
- self.font.info().variant.weight
- }));
- 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;
+ fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs;
}
impl Scaled for i16 {
- fn scaled(self, ctx: &MathContext) -> Abs {
- ctx.font.to_em(self).scaled(ctx)
+ fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs {
+ ctx.font.to_em(self).at(font_size)
}
}
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)
+ fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs {
+ ctx.font.to_em(self).at(font_size)
}
}
impl Scaled for MathValue<'_> {
- fn scaled(self, ctx: &MathContext) -> Abs {
- self.value.scaled(ctx)
+ fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs {
+ self.value.scaled(ctx, font_size)
}
}
diff --git a/crates/typst/src/math/equation.rs b/crates/typst/src/math/equation.rs
index 227fdc3f..fa7e22ab 100644
--- a/crates/typst/src/math/equation.rs
+++ b/crates/typst/src/math/equation.rs
@@ -1,5 +1,7 @@
use std::num::NonZeroUsize;
+use unicode_math_class::MathClass;
+
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
@@ -11,7 +13,7 @@ use crate::layout::{
Abs, AlignElem, Alignment, Axes, Dir, Em, FixedAlignment, Frame, LayoutMultiple,
LayoutSingle, Point, Regions, Size,
};
-use crate::math::{LayoutMath, MathContext};
+use crate::math::{scaled_font_size, LayoutMath, MathContext, MathSize, MathVariant};
use crate::model::{Numbering, Outlinable, ParElem, Refable, Supplement};
use crate::syntax::Span;
use crate::text::{
@@ -94,6 +96,33 @@ pub struct EquationElem {
/// The contents of the equation.
#[required]
pub body: Content,
+
+ /// The size of the glyphs.
+ #[internal]
+ #[default(MathSize::Text)]
+ pub size: MathSize,
+
+ /// The style variant to select.
+ #[internal]
+ pub variant: MathVariant,
+
+ /// Affects the height of exponents.
+ #[internal]
+ #[default(false)]
+ pub cramped: bool,
+
+ /// Whether to use bold glyphs.
+ #[internal]
+ #[default(false)]
+ pub bold: bool,
+
+ /// Whether to use italic glyphs.
+ #[internal]
+ pub italic: Smart<bool>,
+
+ /// A forced class to use for all fragment.
+ #[internal]
+ pub class: Option<MathClass>,
}
impl Synthesize for Packed<EquationElem> {
@@ -124,6 +153,7 @@ impl Finalize for Packed<EquationElem> {
let mut realized = realized;
if self.block(style) {
realized = realized.styled(AlignElem::set_alignment(Alignment::CENTER));
+ realized = realized.styled(EquationElem::set_size(MathSize::Display));
}
realized
.styled(TextElem::set_weight(FontWeight::from_number(450)))
@@ -162,19 +192,19 @@ impl Packed<EquationElem> {
// Find a math font.
let font = find_math_font(engine, styles, self.span())?;
- let mut ctx = MathContext::new(engine, styles, regions, &font, false);
- let rows = ctx.layout_root(self)?;
+ let mut ctx = MathContext::new(engine, styles, regions, &font);
+ let rows = ctx.layout_root(self, styles)?;
let mut items = if rows.row_count() == 1 {
rows.into_par_items()
} else {
- vec![MathParItem::Frame(rows.into_fragment(&ctx).into_frame())]
+ vec![MathParItem::Frame(rows.into_fragment(&ctx, styles).into_frame())]
};
for item in &mut items {
let MathParItem::Frame(frame) = item else { continue };
- let font_size = TextElem::size_in(styles);
+ let font_size = scaled_font_size(&ctx, styles);
let slack = ParElem::leading_in(styles) * 0.7;
let top_edge = TextElem::top_edge_in(styles).resolve(font_size, &font, None);
let bottom_edge =
@@ -205,8 +235,8 @@ impl LayoutSingle for Packed<EquationElem> {
// Find a math font.
let font = find_math_font(engine, styles, self.span())?;
- let mut ctx = MathContext::new(engine, styles, regions, &font, true);
- let mut frame = ctx.layout_frame(self)?;
+ let mut ctx = MathContext::new(engine, styles, regions, &font);
+ let mut frame = ctx.layout_frame(self, styles)?;
if let Some(numbering) = (**self).numbering(styles) {
let pod = Regions::one(regions.base(), Axes::splat(false));
@@ -341,8 +371,8 @@ impl Outlinable for Packed<EquationElem> {
impl LayoutMath for Packed<EquationElem> {
#[typst_macros::time(name = "math.equation", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- self.body().layout_math(ctx)
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
+ self.body().layout_math(ctx, styles)
}
}
diff --git a/crates/typst/src/math/frac.rs b/crates/typst/src/math/frac.rs
index 50185de2..8ea2d09b 100644
--- a/crates/typst/src/math/frac.rs
+++ b/crates/typst/src/math/frac.rs
@@ -1,9 +1,9 @@
use crate::diag::{bail, SourceResult};
-use crate::foundations::{elem, Content, Packed, Value};
+use crate::foundations::{elem, Content, Packed, StyleChain, Value};
use crate::layout::{Em, Frame, FrameItem, Point, Size};
use crate::math::{
- FrameFragment, GlyphFragment, LayoutMath, MathContext, MathSize, Scaled,
- DELIM_SHORT_FALL,
+ scaled_font_size, style_for_denominator, style_for_numerator, FrameFragment,
+ GlyphFragment, LayoutMath, MathContext, Scaled, DELIM_SHORT_FALL,
};
use crate::syntax::{Span, Spanned};
use crate::text::TextElem;
@@ -37,8 +37,15 @@ pub struct FracElem {
impl LayoutMath for Packed<FracElem> {
#[typst_macros::time(name = "math.frac", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- layout(ctx, self.num(), std::slice::from_ref(self.denom()), false, self.span())
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
+ layout(
+ ctx,
+ styles,
+ self.num(),
+ std::slice::from_ref(self.denom()),
+ false,
+ self.span(),
+ )
}
}
@@ -71,55 +78,58 @@ pub struct BinomElem {
impl LayoutMath for Packed<BinomElem> {
#[typst_macros::time(name = "math.binom", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- layout(ctx, self.upper(), self.lower(), true, self.span())
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
+ layout(ctx, styles, self.upper(), self.lower(), true, self.span())
}
}
/// Layout a fraction or binomial.
fn layout(
ctx: &mut MathContext,
+ styles: StyleChain,
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 font_size = scaled_font_size(ctx, styles);
+ let short_fall = DELIM_SHORT_FALL.at(font_size);
+ let axis = scaled!(ctx, styles, axis_height);
+ let thickness = scaled!(ctx, styles, fraction_rule_thickness);
let shift_up = scaled!(
- ctx,
+ ctx, styles,
text: fraction_numerator_shift_up,
display: fraction_numerator_display_style_shift_up,
);
let shift_down = scaled!(
- ctx,
+ ctx, styles,
text: fraction_denominator_shift_down,
display: fraction_denominator_display_style_shift_down,
);
let num_min = scaled!(
- ctx,
+ ctx, styles,
text: fraction_numerator_gap_min,
display: fraction_num_display_style_gap_min,
);
let denom_min = scaled!(
- ctx,
+ ctx, styles,
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();
+ let num_style = style_for_numerator(styles);
+ let num = ctx.layout_frame(num, styles.chain(&num_style))?;
- ctx.style(ctx.style.for_denominator());
- let denom = ctx.layout_frame(&Content::sequence(
- // Add a comma between each element.
- denom.iter().flat_map(|a| [TextElem::packed(','), a.clone()]).skip(1),
- ))?;
- ctx.unstyle();
+ let denom_style = style_for_denominator(styles);
+ let denom = ctx.layout_frame(
+ &Content::sequence(
+ // Add a comma between each element.
+ denom.iter().flat_map(|a| [TextElem::packed(','), a.clone()]).skip(1),
+ ),
+ styles.chain(&denom_style),
+ )?;
- let around = FRAC_AROUND.scaled(ctx);
+ let around = FRAC_AROUND.at(font_size);
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);
@@ -139,13 +149,13 @@ fn layout(
frame.push_frame(denom_pos, denom);
if binom {
- let mut left =
- GlyphFragment::new(ctx, '(', span).stretch_vertical(ctx, height, short_fall);
+ let mut left = GlyphFragment::new(ctx, styles, '(', span)
+ .stretch_vertical(ctx, height, short_fall);
left.center_on_axis(ctx);
ctx.push(left);
- ctx.push(FrameFragment::new(ctx, frame));
- let mut right =
- GlyphFragment::new(ctx, ')', span).stretch_vertical(ctx, height, short_fall);
+ ctx.push(FrameFragment::new(ctx, styles, frame));
+ let mut right = GlyphFragment::new(ctx, styles, ')', span)
+ .stretch_vertical(ctx, height, short_fall);
right.center_on_axis(ctx);
ctx.push(right);
} else {
@@ -154,14 +164,14 @@ fn layout(
FrameItem::Shape(
Geometry::Line(Point::with_x(line_width)).stroked(
FixedStroke::from_pair(
- TextElem::fill_in(ctx.styles()).as_decoration(),
+ TextElem::fill_in(styles).as_decoration(),
thickness,
),
),
span,
),
);
- ctx.push(FrameFragment::new(ctx, frame));
+ ctx.push(FrameFragment::new(ctx, styles, frame));
}
Ok(())
diff --git a/crates/typst/src/math/fragment.rs b/crates/typst/src/math/fragment.rs
index 64bc3078..e78cd95a 100644
--- a/crates/typst/src/math/fragment.rs
+++ b/crates/typst/src/math/fragment.rs
@@ -5,10 +5,12 @@ use ttf_parser::gsub::AlternateSet;
use ttf_parser::{GlyphId, Rect};
use unicode_math_class::MathClass;
-use crate::foundations::Smart;
+use crate::foundations::StyleChain;
use crate::introspection::{Meta, MetaElem};
use crate::layout::{Abs, Corner, Em, Frame, FrameItem, Point, Size};
-use crate::math::{Limits, MathContext, MathStyle, Scaled};
+use crate::math::{
+ scaled_font_size, styled_char, EquationElem, Limits, MathContext, MathSize, Scaled,
+};
use crate::syntax::Span;
use crate::text::{Font, Glyph, Lang, TextElem, TextItem};
use crate::visualize::Paint;
@@ -67,20 +69,23 @@ impl MathFragment {
}
}
- pub fn class(&self) -> Option<MathClass> {
- self.style().and_then(|style| style.class.as_custom()).or(match self {
+ pub fn class(&self) -> MathClass {
+ match self {
Self::Glyph(glyph) => glyph.class,
Self::Variant(variant) => variant.class,
- Self::Frame(fragment) => Some(fragment.class),
- _ => None,
- })
+ Self::Frame(fragment) => fragment.class,
+ Self::Spacing(_) => MathClass::Space,
+ Self::Space(_) => MathClass::Space,
+ Self::Linebreak => MathClass::Space,
+ Self::Align => MathClass::Special,
+ }
}
- pub fn style(&self) -> Option<MathStyle> {
+ pub fn math_size(&self) -> Option<MathSize> {
match self {
- Self::Glyph(glyph) => Some(glyph.style),
- Self::Variant(variant) => Some(variant.style),
- Self::Frame(fragment) => Some(fragment.style),
+ Self::Glyph(glyph) => Some(glyph.math_size),
+ Self::Variant(variant) => Some(variant.math_size),
+ Self::Frame(fragment) => Some(fragment.math_size),
_ => None,
}
}
@@ -95,27 +100,10 @@ impl MathFragment {
}
pub fn set_class(&mut self, class: MathClass) {
- macro_rules! set_style_class {
- ($fragment:ident) => {
- if $fragment.style.class.is_custom() {
- $fragment.style.class = Smart::Custom(class);
- }
- };
- }
-
match self {
- Self::Glyph(glyph) => {
- glyph.class = Some(class);
- set_style_class!(glyph);
- }
- Self::Variant(variant) => {
- variant.class = Some(class);
- set_style_class!(variant);
- }
- Self::Frame(fragment) => {
- fragment.class = class;
- set_style_class!(fragment);
- }
+ Self::Glyph(glyph) => glyph.class = class,
+ Self::Variant(variant) => variant.class = class,
+ Self::Frame(fragment) => fragment.class = class,
_ => {}
}
}
@@ -130,21 +118,22 @@ impl MathFragment {
}
pub fn is_spaced(&self) -> bool {
- match self {
- MathFragment::Frame(frame) => {
- match self.style().and_then(|style| style.class.as_custom()) {
- Some(MathClass::Fence) => true,
- Some(_) => false,
- None => frame.spaced,
+ self.class() == MathClass::Fence
+ || match self {
+ MathFragment::Frame(frame) => {
+ frame.spaced
+ && matches!(
+ frame.class,
+ MathClass::Normal | MathClass::Alphabetic
+ )
}
+ _ => false,
}
- _ => self.class() == Some(MathClass::Fence),
- }
}
pub fn is_text_like(&self) -> bool {
match self {
- Self::Glyph(_) | Self::Variant(_) => self.class() != Some(MathClass::Large),
+ Self::Glyph(_) | Self::Variant(_) => self.class() != MathClass::Large,
MathFragment::Frame(frame) => frame.text_like,
_ => false,
}
@@ -224,43 +213,57 @@ pub struct GlyphFragment {
pub descent: Abs,
pub italics_correction: Abs,
pub accent_attach: Abs,
- pub style: MathStyle,
pub font_size: Abs,
- pub class: Option<MathClass>,
+ pub class: MathClass,
+ pub math_size: MathSize,
pub span: Span,
pub meta: SmallVec<[Meta; 1]>,
pub limits: Limits,
}
impl GlyphFragment {
- pub fn new(ctx: &MathContext, c: char, span: Span) -> Self {
+ pub fn new(ctx: &MathContext, styles: StyleChain, c: char, span: Span) -> Self {
let id = ctx.ttf.glyph_index(c).unwrap_or_default();
let id = Self::adjust_glyph_index(ctx, id);
- Self::with_id(ctx, c, id, span)
+ Self::with_id(ctx, styles, c, id, span)
}
- pub fn try_new(ctx: &MathContext, c: char, span: Span) -> Option<Self> {
- let c = ctx.style.styled_char(c);
+ pub fn try_new(
+ ctx: &MathContext,
+ styles: StyleChain,
+ c: char,
+ span: Span,
+ ) -> Option<Self> {
+ let c = styled_char(styles, c);
let id = ctx.ttf.glyph_index(c)?;
let id = Self::adjust_glyph_index(ctx, id);
- Some(Self::with_id(ctx, c, id, span))
- }
+ Some(Self::with_id(ctx, styles, c, id, span))
+ }
+
+ pub fn with_id(
+ ctx: &MathContext,
+ styles: StyleChain,
+ c: char,
+ id: GlyphId,
+ span: Span,
+ ) -> Self {
+ let class = EquationElem::class_in(styles)
+ .or_else(|| match c {
+ ':' => Some(MathClass::Relation),
+ '.' | '/' | '⋯' | '⋱' | '⋰' | '⋮' => Some(MathClass::Normal),
+ _ => unicode_math_class::class(c),
+ })
+ .unwrap_or(MathClass::Normal);
- pub fn with_id(ctx: &MathContext, c: char, id: GlyphId, span: Span) -> Self {
- let class = match c {
- ':' => Some(MathClass::Relation),
- '.' | '/' | '⋯' | '⋱' | '⋰' | '⋮' => Some(MathClass::Normal),
- _ => 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()).as_decoration(),
- shift: TextElem::baseline_in(ctx.styles()),
- style: ctx.style,
- font_size: ctx.size,
+ lang: TextElem::lang_in(styles),
+ fill: TextElem::fill_in(styles).as_decoration(),
+ shift: TextElem::baseline_in(styles),
+ font_size: scaled_font_size(ctx, styles),
+ math_size: EquationElem::size_in(styles),
width: Abs::zero(),
ascent: Abs::zero(),
descent: Abs::zero(),
@@ -269,7 +272,7 @@ impl GlyphFragment {
accent_attach: Abs::zero(),
class,
span,
- meta: MetaElem::data_in(ctx.styles()),
+ meta: MetaElem::data_in(styles),
};
fragment.set_id(ctx, id);
fragment
@@ -288,7 +291,7 @@ impl GlyphFragment {
/// 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 italics = italics_correction(ctx, id, self.font_size).unwrap_or_default();
let bbox = ctx.ttf.glyph_bounding_box(id).unwrap_or(Rect {
x_min: 0,
y_min: 0,
@@ -296,8 +299,9 @@ impl GlyphFragment {
y_max: 0,
});
- let mut width = advance.scaled(ctx);
- let accent_attach = accent_attach(ctx, id).unwrap_or((width + italics) / 2.0);
+ let mut width = advance.scaled(ctx, self.font_size);
+ let accent_attach =
+ accent_attach(ctx, id, self.font_size).unwrap_or((width + italics) / 2.0);
if !is_extended_shape(ctx, id) {
width += italics;
@@ -305,8 +309,8 @@ impl GlyphFragment {
self.id = id;
self.width = width;
- self.ascent = bbox.y_max.scaled(ctx);
- self.descent = -bbox.y_min.scaled(ctx);
+ self.ascent = bbox.y_max.scaled(ctx, self.font_size);
+ self.descent = -bbox.y_min.scaled(ctx, self.font_size);
self.italics_correction = italics;
self.accent_attach = accent_attach;
}
@@ -319,11 +323,11 @@ impl GlyphFragment {
VariantFragment {
c: self.c,
id: Some(self.id),
- style: self.style,
font_size: self.font_size,
italics_correction: self.italics_correction,
accent_attach: self.accent_attach,
class: self.class,
+ math_size: self.math_size,
span: self.span,
limits: self.limits,
frame: self.into_frame(),
@@ -388,9 +392,9 @@ pub struct VariantFragment {
pub italics_correction: Abs,
pub accent_attach: Abs,
pub frame: Frame,
- pub style: MathStyle,
pub font_size: Abs,
- pub class: Option<MathClass>,
+ pub class: MathClass,
+ pub math_size: MathSize,
pub span: Span,
pub limits: Limits,
pub mid_stretched: Option<bool>,
@@ -401,7 +405,8 @@ impl VariantFragment {
/// on the axis.
pub fn center_on_axis(&mut self, ctx: &MathContext) {
let h = self.frame.height();
- self.frame.set_baseline(h / 2.0 + scaled!(ctx, axis_height));
+ let axis = ctx.constants.axis_height().scaled(ctx, self.font_size);
+ self.frame.set_baseline(h / 2.0 + axis);
}
}
@@ -414,9 +419,9 @@ impl Debug for VariantFragment {
#[derive(Debug, Clone)]
pub struct FrameFragment {
pub frame: Frame,
- pub style: MathStyle,
pub font_size: Abs,
pub class: MathClass,
+ pub math_size: MathSize,
pub limits: Limits,
pub spaced: bool,
pub base_ascent: Abs,
@@ -426,15 +431,15 @@ pub struct FrameFragment {
}
impl FrameFragment {
- pub fn new(ctx: &MathContext, mut frame: Frame) -> Self {
+ pub fn new(ctx: &MathContext, styles: StyleChain, mut frame: Frame) -> Self {
let base_ascent = frame.ascent();
let accent_attach = frame.width() / 2.0;
- frame.meta(ctx.styles(), false);
+ frame.meta(styles, false);
Self {
frame,
- font_size: ctx.size,
- style: ctx.style,
- class: MathClass::Normal,
+ font_size: scaled_font_size(ctx, styles),
+ class: EquationElem::class_in(styles).unwrap_or(MathClass::Normal),
+ math_size: EquationElem::size_in(styles),
limits: Limits::Never,
spaced: false,
base_ascent,
@@ -480,13 +485,25 @@ pub struct SpacingFragment {
}
/// 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))
+fn italics_correction(ctx: &MathContext, id: GlyphId, font_size: Abs) -> Option<Abs> {
+ Some(
+ ctx.table
+ .glyph_info?
+ .italic_corrections?
+ .get(id)?
+ .scaled(ctx, font_size),
+ )
}
/// Loop up the top accent attachment position for a glyph.
-fn accent_attach(ctx: &MathContext, id: GlyphId) -> Option<Abs> {
- Some(ctx.table.glyph_info?.top_accent_attachments?.get(id)?.scaled(ctx))
+fn accent_attach(ctx: &MathContext, id: GlyphId, font_size: Abs) -> Option<Abs> {
+ Some(
+ ctx.table
+ .glyph_info?
+ .top_accent_attachments?
+ .get(id)?
+ .scaled(ctx, font_size),
+ )
}
/// Look up the script/scriptscript alternates for a glyph
@@ -515,6 +532,7 @@ fn is_extended_shape(ctx: &MathContext, id: GlyphId) -> bool {
#[allow(unused)]
fn kern_at_height(
ctx: &MathContext,
+ font_size: Abs,
id: GlyphId,
corner: Corner,
height: Abs,
@@ -528,9 +546,9 @@ fn kern_at_height(
}?;
let mut i = 0;
- while i < kern.count() && height > kern.height(i)?.scaled(ctx) {
+ while i < kern.count() && height > kern.height(i)?.scaled(ctx, font_size) {
i += 1;
}
- Some(kern.kern(i)?.scaled(ctx))
+ Some(kern.kern(i)?.scaled(ctx, font_size))
}
diff --git a/crates/typst/src/math/lr.rs b/crates/typst/src/math/lr.rs
index 6d2bdbf1..a34c934f 100644
--- a/crates/typst/src/math/lr.rs
+++ b/crates/typst/src/math/lr.rs
@@ -1,7 +1,9 @@
use unicode_math_class::MathClass;
use crate::diag::SourceResult;
-use crate::foundations::{elem, func, Content, NativeElement, Packed, Resolve, Smart};
+use crate::foundations::{
+ elem, func, Content, NativeElement, Packed, Resolve, Smart, StyleChain,
+};
use crate::layout::{Abs, Em, Length, Rel};
use crate::math::{
GlyphFragment, LayoutMath, MathContext, MathFragment, Scaled, SpacingFragment,
@@ -37,16 +39,16 @@ pub struct LrElem {
impl LayoutMath for Packed<LrElem> {
#[typst_macros::time(name = "math.lr", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
let mut body = self.body();
if let Some(elem) = body.to_packed::<LrElem>() {
- if elem.size(ctx.styles()).is_auto() {
+ if elem.size(styles).is_auto() {
body = elem.body();
}
}
- let mut fragments = ctx.layout_fragments(body)?;
- let axis = scaled!(ctx, axis_height);
+ let mut fragments = ctx.layout_fragments(body, styles)?;
+ let axis = scaled!(ctx, styles, axis_height);
let max_extent = fragments
.iter()
.map(|fragment| (fragment.ascent() - axis).max(fragment.descent() + axis))
@@ -54,17 +56,17 @@ impl LayoutMath for Packed<LrElem> {
.unwrap_or_default();
let height = self
- .size(ctx.styles())
+ .size(styles)
.unwrap_or(Rel::one())
- .resolve(ctx.styles())
+ .resolve(styles)
.relative_to(2.0 * max_extent);
// Scale up fragments at both ends.
match fragments.as_mut_slice() {
- [one] => scale(ctx, one, height, None),
+ [one] => scale(ctx, styles, one, height, None),
[first, .., last] => {
- scale(ctx, first, height, Some(MathClass::Opening));
- scale(ctx, last, height, Some(MathClass::Closing));
+ scale(ctx, styles, first, height, Some(MathClass::Opening));
+ scale(ctx, styles, last, height, Some(MathClass::Closing));
}
_ => {}
}
@@ -74,7 +76,7 @@ impl LayoutMath for Packed<LrElem> {
if let MathFragment::Variant(ref mut variant) = fragment {
if variant.mid_stretched == Some(false) {
variant.mid_stretched = Some(true);
- scale(ctx, fragment, height, Some(MathClass::Large));
+ scale(ctx, styles, fragment, height, Some(MathClass::Large));
}
}
}
@@ -112,8 +114,8 @@ pub struct MidElem {
impl LayoutMath for Packed<MidElem> {
#[typst_macros::time(name = "math.mid", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- let mut fragments = ctx.layout_fragments(self.body())?;
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
+ let mut fragments = ctx.layout_fragments(self.body(), styles)?;
for fragment in &mut fragments {
match fragment {
@@ -137,23 +139,24 @@ impl LayoutMath for Packed<MidElem> {
/// Scale a math fragment to a height.
fn scale(
ctx: &mut MathContext,
+ styles: StyleChain,
fragment: &mut MathFragment,
height: Abs,
apply: Option<MathClass>,
) {
if matches!(
fragment.class(),
- Some(MathClass::Opening | MathClass::Closing | MathClass::Fence)
+ 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)
+ GlyphFragment::new(ctx, styles, variant.c, variant.span)
}
_ => return,
};
- let short_fall = DELIM_SHORT_FALL.scaled(ctx);
+ let short_fall = DELIM_SHORT_FALL.at(glyph.font_size);
let mut stretched = glyph.stretch_vertical(ctx, height, short_fall);
stretched.center_on_axis(ctx);
diff --git a/crates/typst/src/math/matrix.rs b/crates/typst/src/math/matrix.rs
index 24563a45..616cc738 100644
--- a/crates/typst/src/math/matrix.rs
+++ b/crates/typst/src/math/matrix.rs
@@ -9,8 +9,8 @@ use crate::layout::{
Abs, Axes, Em, FixedAlignment, Frame, FrameItem, Length, Point, Ratio, Rel, Size,
};
use crate::math::{
- alignments, stack, AlignmentResult, FrameFragment, GlyphFragment, LayoutMath,
- MathContext, Scaled, DELIM_SHORT_FALL,
+ alignments, scaled_font_size, stack, style_for_denominator, AlignmentResult,
+ FrameFragment, GlyphFragment, LayoutMath, MathContext, Scaled, DELIM_SHORT_FALL,
};
use crate::syntax::{Span, Spanned};
use crate::text::TextElem;
@@ -59,16 +59,19 @@ pub struct VecElem {
impl LayoutMath for Packed<VecElem> {
#[typst_macros::time(name = "math.vec", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- let delim = self.delim(ctx.styles());
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
+ let delim = self.delim(styles);
let frame = layout_vec_body(
ctx,
+ styles,
self.children(),
FixedAlignment::Center,
- self.gap(ctx.styles()),
+ self.gap(styles),
)?;
+
layout_delimiters(
ctx,
+ styles,
frame,
delim.map(Delimiter::open),
delim.map(Delimiter::close),
@@ -212,8 +215,8 @@ pub struct MatElem {
impl LayoutMath for Packed<MatElem> {
#[typst_macros::time(name = "math.mat", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- let augment = self.augment(ctx.styles());
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
+ let augment = self.augment(styles);
let rows = self.rows();
if let Some(aug) = &augment {
@@ -242,17 +245,19 @@ impl LayoutMath for Packed<MatElem> {
}
}
- let delim = self.delim(ctx.styles());
+ let delim = self.delim(styles);
let frame = layout_mat_body(
ctx,
+ styles,
rows,
augment,
- Axes::new(self.column_gap(ctx.styles()), self.row_gap(ctx.styles())),
+ Axes::new(self.column_gap(styles), self.row_gap(styles)),
self.span(),
)?;
layout_delimiters(
ctx,
+ styles,
frame,
delim.map(Delimiter::open),
delim.map(Delimiter::close),
@@ -311,22 +316,23 @@ pub struct CasesElem {
impl LayoutMath for Packed<CasesElem> {
#[typst_macros::time(name = "math.cases", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- let delim = self.delim(ctx.styles());
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
+ let delim = self.delim(styles);
let frame = layout_vec_body(
ctx,
+ styles,
self.children(),
FixedAlignment::Start,
- self.gap(ctx.styles()),
+ self.gap(styles),
)?;
- let (open, close) = if self.reverse(ctx.styles()) {
+ let (open, close) = if self.reverse(styles) {
(None, Some(delim.close()))
} else {
(Some(delim.open()), None)
};
- layout_delimiters(ctx, frame, open, close, self.span())
+ layout_delimiters(ctx, styles, frame, open, close, self.span())
}
}
@@ -377,23 +383,26 @@ impl Delimiter {
/// Layout the inner contents of a vector.
fn layout_vec_body(
ctx: &mut MathContext,
+ styles: StyleChain,
column: &[Content],
align: FixedAlignment,
row_gap: Rel<Abs>,
) -> SourceResult<Frame> {
let gap = row_gap.relative_to(ctx.regions.base().y);
- ctx.style(ctx.style.for_denominator());
+
+ let denom_style = style_for_denominator(styles);
let mut flat = vec![];
for child in column {
- flat.push(ctx.layout_row(child)?);
+ flat.push(ctx.layout_row(child, styles.chain(&denom_style))?);
}
- ctx.unstyle();
- Ok(stack(ctx, flat, align, gap, 0))
+
+ Ok(stack(ctx, styles, flat, align, gap, 0))
}
/// Layout the inner contents of a matrix.
fn layout_mat_body(
ctx: &mut MathContext,
+ styles: StyleChain,
rows: &[Vec<Content>],
augment: Option<Augment<Abs>>,
gap: Axes<Rel<Abs>>,
@@ -406,10 +415,11 @@ fn layout_mat_body(
// with font size to ensure that augmentation lines
// look correct by default at all matrix sizes.
// The line cap is also set to square because it looks more "correct".
- let default_stroke_thickness = DEFAULT_STROKE_THICKNESS.scaled(ctx);
+ let font_size = scaled_font_size(ctx, styles);
+ let default_stroke_thickness = DEFAULT_STROKE_THICKNESS.at(font_size);
let default_stroke = FixedStroke {
thickness: default_stroke_thickness,
- paint: TextElem::fill_in(ctx.styles()).as_decoration(),
+ paint: TextElem::fill_in(styles).as_decoration(),
cap: LineCap::Square,
..Default::default()
};
@@ -443,10 +453,10 @@ fn layout_mat_body(
// individual cells are then added to it.
let mut cols = vec![vec![]; ncols];
- ctx.style(ctx.style.for_denominator());
+ let denom_style = style_for_denominator(styles);
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)?;
+ let cell = ctx.layout_row(cell, styles.chain(&denom_style))?;
ascent.set_max(cell.ascent());
descent.set_max(cell.descent());
@@ -454,7 +464,6 @@ fn layout_mat_body(
col.push(cell);
}
}
- ctx.unstyle();
// For each row, combine maximum ascent and descent into a row height.
// Sum the row heights, then add the total height of the gaps between rows.
@@ -472,7 +481,8 @@ fn layout_mat_body(
let mut y = Abs::zero();
for (cell, &(ascent, descent)) in col.into_iter().zip(&heights) {
- let cell = cell.into_aligned_frame(ctx, &points, FixedAlignment::Center);
+ let cell =
+ cell.into_aligned_frame(ctx, styles, &points, FixedAlignment::Center);
let pos = Point::new(
if points.is_empty() { x + (rcol - cell.width()) / 2.0 } else { x },
y + ascent - cell.ascent(),
@@ -542,28 +552,30 @@ fn line_item(length: Abs, vertical: bool, stroke: FixedStroke, span: Span) -> Fr
/// Layout the outer wrapper around the body of a vector or matrix.
fn layout_delimiters(
ctx: &mut MathContext,
+ styles: StyleChain,
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 font_size = scaled_font_size(ctx, styles);
+ let short_fall = DELIM_SHORT_FALL.at(font_size);
+ let axis = ctx.constants.axis_height().scaled(ctx, font_size);
let height = frame.height();
let target = height + VERTICAL_PADDING.of(height);
frame.set_baseline(height / 2.0 + axis);
if let Some(left) = left {
- let mut left =
- GlyphFragment::new(ctx, left, span).stretch_vertical(ctx, target, short_fall);
+ let mut left = GlyphFragment::new(ctx, styles, left, span)
+ .stretch_vertical(ctx, target, short_fall);
left.center_on_axis(ctx);
ctx.push(left);
}
- ctx.push(FrameFragment::new(ctx, frame));
+ ctx.push(FrameFragment::new(ctx, styles, frame));
if let Some(right) = right {
- let mut right = GlyphFragment::new(ctx, right, span)
+ let mut right = GlyphFragment::new(ctx, styles, right, span)
.stretch_vertical(ctx, target, short_fall);
right.center_on_axis(ctx);
ctx.push(right);
diff --git a/crates/typst/src/math/mod.rs b/crates/typst/src/math/mod.rs
index ee0a3825..3338fb63 100644
--- a/crates/typst/src/math/mod.rs
+++ b/crates/typst/src/math/mod.rs
@@ -6,7 +6,8 @@ mod accent;
mod align;
mod attach;
mod cancel;
-mod class;
+#[path = "class.rs"]
+mod class_;
mod equation;
mod frac;
mod fragment;
@@ -24,7 +25,7 @@ pub use self::accent::*;
pub use self::align::*;
pub use self::attach::*;
pub use self::cancel::*;
-pub use self::class::*;
+pub use self::class_::*;
pub use self::equation::*;
pub use self::frac::*;
pub use self::lr::*;
@@ -46,7 +47,7 @@ use crate::foundations::{
category, Category, Content, Module, Resolve, Scope, StyleChain,
};
use crate::layout::{BoxElem, HElem, Spacing};
-use crate::realize::BehavedBuilder;
+use crate::realize::{realize, BehavedBuilder};
use crate::text::{LinebreakElem, SpaceElem, TextElem};
/// Typst has special [syntax]($syntax/#math) and library functions to typeset
@@ -215,12 +216,12 @@ pub fn module() -> Module {
/// Layout for math elements.
pub trait LayoutMath {
/// Layout the element, producing fragment in the context.
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()>;
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()>;
}
impl LayoutMath for Content {
#[typst_macros::time(name = "math", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
// Directly layout the body of nested equations instead of handling it
// like a normal equation so that things like this work:
// ```
@@ -228,11 +229,11 @@ impl LayoutMath for Content {
// $ my r^2 $
// ```
if let Some(elem) = self.to_packed::<EquationElem>() {
- return elem.layout_math(ctx);
+ return elem.layout_math(ctx, styles);
}
- if let Some(realized) = ctx.realize(self)? {
- return realized.layout_math(ctx);
+ if let Some(realized) = realize(ctx.engine, self, styles)? {
+ return realized.layout_math(ctx, styles);
}
if self.is_sequence() {
@@ -242,32 +243,28 @@ impl LayoutMath for Content {
});
for (child, _) in bb.finish().0.iter() {
- child.layout_math(ctx)?;
+ child.layout_math(ctx, styles)?;
}
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));
+ if let Some((elem, local)) = self.to_styled() {
+ let outer = styles;
+ let styles = outer.chain(local);
+
+ if TextElem::font_in(styles) != TextElem::font_in(outer) {
+ let frame = ctx.layout_content(elem, styles)?;
+ ctx.push(FrameFragment::new(ctx, styles, 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;
+ elem.layout_math(ctx, styles)?;
return Ok(());
}
if self.is::<SpaceElem>() {
- ctx.push(MathFragment::Space(ctx.space_width.scaled(ctx)));
+ let font_size = scaled_font_size(ctx, styles);
+ ctx.push(MathFragment::Space(ctx.space_width.at(font_size)));
return Ok(());
}
@@ -280,8 +277,8 @@ impl LayoutMath for Content {
if let Spacing::Rel(rel) = elem.amount() {
if rel.rel.is_zero() {
ctx.push(SpacingFragment {
- width: rel.abs.resolve(ctx.styles()),
- weak: elem.weak(ctx.styles()),
+ width: rel.abs.resolve(styles),
+ weak: elem.weak(styles),
});
}
}
@@ -289,27 +286,27 @@ impl LayoutMath for Content {
}
if let Some(elem) = self.to_packed::<TextElem>() {
- let fragment = ctx.layout_text(elem)?;
+ let fragment = ctx.layout_text(elem, styles)?;
ctx.push(fragment);
return Ok(());
}
if let Some(boxed) = self.to_packed::<BoxElem>() {
- let frame = ctx.layout_box(boxed)?;
- ctx.push(FrameFragment::new(ctx, frame).with_spaced(true));
+ let frame = ctx.layout_box(boxed, styles)?;
+ ctx.push(FrameFragment::new(ctx, styles, frame).with_spaced(true));
return Ok(());
}
if let Some(elem) = self.with::<dyn LayoutMath>() {
- return elem.layout_math(ctx);
+ return elem.layout_math(ctx, styles);
}
- let mut frame = ctx.layout_content(self)?;
+ let mut frame = ctx.layout_content(self, styles)?;
if !frame.has_baseline() {
- let axis = scaled!(ctx, axis_height);
+ let axis = scaled!(ctx, styles, axis_height);
frame.set_baseline(frame.height() / 2.0 + axis);
}
- ctx.push(FrameFragment::new(ctx, frame).with_spaced(true));
+ ctx.push(FrameFragment::new(ctx, styles, frame).with_spaced(true));
Ok(())
}
diff --git a/crates/typst/src/math/op.rs b/crates/typst/src/math/op.rs
index 1d0645ff..a13cec4d 100644
--- a/crates/typst/src/math/op.rs
+++ b/crates/typst/src/math/op.rs
@@ -2,9 +2,9 @@ use ecow::EcoString;
use unicode_math_class::MathClass;
use crate::diag::SourceResult;
-use crate::foundations::{elem, Content, NativeElement, Packed, Scope};
+use crate::foundations::{elem, Content, NativeElement, Packed, Scope, StyleChain};
use crate::layout::HElem;
-use crate::math::{FrameFragment, LayoutMath, Limits, MathContext, MathStyleElem, THIN};
+use crate::math::{upright, FrameFragment, LayoutMath, Limits, MathContext, THIN};
use crate::text::TextElem;
/// A text operator in an equation.
@@ -35,19 +35,19 @@ pub struct OpElem {
impl LayoutMath for Packed<OpElem> {
#[typst_macros::time(name = "math.op", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- let fragment = ctx.layout_fragment(self.text())?;
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
+ let fragment = ctx.layout_fragment(self.text(), styles)?;
let italics = fragment.italics_correction();
let accent_attach = fragment.accent_attach();
let text_like = fragment.is_text_like();
ctx.push(
- FrameFragment::new(ctx, fragment.into_frame())
+ FrameFragment::new(ctx, styles, fragment.into_frame())
.with_class(MathClass::Large)
.with_italics_correction(italics)
.with_accent_attach(accent_attach)
.with_text_like(text_like)
- .with_limits(if self.limits(ctx.styles()) {
+ .with_limits(if self.limits(styles) {
Limits::Display
} else {
Limits::Never
@@ -72,7 +72,7 @@ macro_rules! ops {
let dif = |d| {
HElem::new(THIN.into()).with_weak(true).pack()
- + MathStyleElem::new(TextElem::packed(d)).with_italic(Some(false)).pack()
+ + upright(TextElem::packed(d))
};
math.define("dif", dif('d'));
math.define("Dif", dif('D'));
diff --git a/crates/typst/src/math/root.rs b/crates/typst/src/math/root.rs
index db3a7b85..65060de3 100644
--- a/crates/typst/src/math/root.rs
+++ b/crates/typst/src/math/root.rs
@@ -1,8 +1,11 @@
+use comemo::Prehashed;
+
use crate::diag::SourceResult;
-use crate::foundations::{elem, func, Content, NativeElement, Packed};
+use crate::foundations::{elem, func, Content, NativeElement, Packed, StyleChain};
use crate::layout::{Abs, Frame, FrameItem, Point, Size};
use crate::math::{
- FrameFragment, GlyphFragment, LayoutMath, MathContext, MathSize, Scaled,
+ style_cramped, EquationElem, FrameFragment, GlyphFragment, LayoutMath, MathContext,
+ MathSize, Scaled,
};
use crate::syntax::Span;
use crate::text::TextElem;
@@ -41,8 +44,8 @@ pub struct RootElem {
impl LayoutMath for Packed<RootElem> {
#[typst_macros::time(name = "math.root", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- layout(ctx, self.index(ctx.styles()).as_ref(), self.radicand(), self.span())
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
+ layout(ctx, styles, self.index(styles).as_ref(), self.radicand(), self.span())
}
}
@@ -52,36 +55,37 @@ impl LayoutMath for Packed<RootElem> {
/// See also: https://www.w3.org/TR/mathml-core/#radicals-msqrt-mroot
fn layout(
ctx: &mut MathContext,
+ styles: StyleChain,
index: Option<&Content>,
radicand: &Content,
span: Span,
) -> SourceResult<()> {
let gap = scaled!(
- ctx,
+ ctx, styles,
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 thickness = scaled!(ctx, styles, radical_rule_thickness);
+ let extra_ascender = scaled!(ctx, styles, radical_extra_ascender);
+ let kern_before = scaled!(ctx, styles, radical_kern_before_degree);
+ let kern_after = scaled!(ctx, styles, 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();
+ let cramped = style_cramped();
+ let radicand = ctx.layout_frame(radicand, styles.chain(&cramped))?;
// Layout root symbol.
let target = radicand.height() + thickness + gap;
- let sqrt = GlyphFragment::new(ctx, '√', span)
+ let sqrt = GlyphFragment::new(ctx, styles, '√', span)
.stretch_vertical(ctx, target, Abs::zero())
.frame;
// Layout the index.
- ctx.style(ctx.style.with_size(MathSize::ScriptScript));
- let index = index.map(|elem| ctx.layout_frame(elem)).transpose()?;
- ctx.unstyle();
+ let sscript = Prehashed::new(EquationElem::set_size(MathSize::ScriptScript));
+ let index = index
+ .map(|elem| ctx.layout_frame(elem, styles.chain(&sscript)))
+ .transpose()?;
// TeXbook, page 443, item 11
// Keep original gap, and then distribute any remaining free space
@@ -133,7 +137,7 @@ fn layout(
FrameItem::Shape(
Geometry::Line(Point::with_x(radicand.width())).stroked(
FixedStroke::from_pair(
- TextElem::fill_in(ctx.styles()).as_decoration(),
+ TextElem::fill_in(styles).as_decoration(),
thickness,
),
),
@@ -142,7 +146,7 @@ fn layout(
);
frame.push_frame(radicand_pos, radicand);
- ctx.push(FrameFragment::new(ctx, frame));
+ ctx.push(FrameFragment::new(ctx, styles, frame));
Ok(())
}
diff --git a/crates/typst/src/math/row.rs b/crates/typst/src/math/row.rs
index 87ac154d..89e1695e 100644
--- a/crates/typst/src/math/row.rs
+++ b/crates/typst/src/math/row.rs
@@ -2,11 +2,11 @@ use std::iter::once;
use unicode_math_class::MathClass;
-use crate::foundations::Resolve;
+use crate::foundations::{Resolve, StyleChain};
use crate::layout::{Abs, AlignElem, Em, FixedAlignment, Frame, FrameKind, Point, Size};
use crate::math::{
- alignments, spacing, AlignmentResult, FrameFragment, MathContext, MathFragment,
- MathParItem, MathSize, Scaled,
+ alignments, scaled_font_size, spacing, AlignmentResult, EquationElem, FrameFragment,
+ MathContext, MathFragment, MathParItem, MathSize,
};
use crate::model::ParElem;
@@ -61,9 +61,9 @@ impl MathRow {
// 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)
+ if fragment.class() == MathClass::Vary
&& matches!(
- last.and_then(|i| resolved[i].class()),
+ last.map(|i| resolved[i].class()),
Some(
MathClass::Normal
| MathClass::Alphabetic
@@ -131,8 +131,8 @@ impl MathRow {
if self.0.len() == 1 {
self.0
.first()
- .and_then(|fragment| fragment.class())
- .unwrap_or(MathClass::Special)
+ .map(|fragment| fragment.class())
+ .unwrap_or(MathClass::Normal)
} else {
// FrameFragment::new() (inside 'into_fragment' in this branch) defaults
// to MathClass::Normal for its class.
@@ -140,23 +140,23 @@ impl MathRow {
}
}
- pub fn into_frame(self, ctx: &MathContext) -> Frame {
- let styles = ctx.styles();
+ pub fn into_frame(self, ctx: &MathContext, styles: StyleChain) -> Frame {
let align = AlignElem::alignment_in(styles).resolve(styles).x;
- self.into_aligned_frame(ctx, &[], align)
+ self.into_aligned_frame(ctx, styles, &[], align)
}
- pub fn into_fragment(self, ctx: &MathContext) -> MathFragment {
+ pub fn into_fragment(self, ctx: &MathContext, styles: StyleChain) -> MathFragment {
if self.0.len() == 1 {
self.0.into_iter().next().unwrap()
} else {
- FrameFragment::new(ctx, self.into_frame(ctx)).into()
+ FrameFragment::new(ctx, styles, self.into_frame(ctx, styles)).into()
}
}
pub fn into_aligned_frame(
self,
ctx: &MathContext,
+ styles: StyleChain,
points: &[Abs],
align: FixedAlignment,
) -> Frame {
@@ -164,10 +164,11 @@ impl MathRow {
return self.into_line_frame(points, align);
}
- let leading = if ctx.style.size >= MathSize::Text {
- ParElem::leading_in(ctx.styles())
+ let leading = if EquationElem::size_in(styles) >= MathSize::Text {
+ ParElem::leading_in(styles)
} else {
- TIGHT_LEADING.scaled(ctx)
+ let font_size = scaled_font_size(ctx, styles);
+ TIGHT_LEADING.at(font_size)
};
let mut rows: Vec<_> = self.rows();
@@ -272,8 +273,7 @@ impl MathRow {
let mut space_is_visible = false;
- let is_relation =
- |f: &MathFragment| matches!(f.class(), Some(MathClass::Relation));
+ let is_relation = |f: &MathFragment| matches!(f.class(), MathClass::Relation);
let is_space = |f: &MathFragment| {
matches!(f, MathFragment::Space(_) | MathFragment::Spacing(_))
};
@@ -302,8 +302,8 @@ impl MathRow {
frame.push_frame(pos, fragment.into_frame());
empty = false;
- if class == Some(MathClass::Binary)
- || (class == Some(MathClass::Relation)
+ if class == MathClass::Binary
+ || (class == MathClass::Relation
&& !iter.peek().map(is_relation).unwrap_or_default())
{
let mut frame_prev = std::mem::replace(
diff --git a/crates/typst/src/math/spacing.rs b/crates/typst/src/math/spacing.rs
index c6c697fe..1e809016 100644
--- a/crates/typst/src/math/spacing.rs
+++ b/crates/typst/src/math/spacing.rs
@@ -27,15 +27,14 @@ pub(super) fn spacing(
) -> Option<MathFragment> {
use MathClass::*;
- let class = |f: &MathFragment| f.class().unwrap_or(Special);
let resolve = |v: Em, size_ref: &MathFragment| -> Option<MathFragment> {
let width = size_ref.font_size().map_or(Abs::zero(), |size| v.at(size));
Some(SpacingFragment { width, weak: false }.into())
};
let script =
- |f: &MathFragment| f.style().map_or(false, |s| s.size <= MathSize::Script);
+ |f: &MathFragment| f.math_size().map_or(false, |s| s <= MathSize::Script);
- match (class(l), class(r)) {
+ match (l.class(), r.class()) {
// No spacing before punctuation; thin spacing after punctuation, unless
// in script size.
(_, Punctuation) => None,
diff --git a/crates/typst/src/math/stretch.rs b/crates/typst/src/math/stretch.rs
index 90ed03b7..f05e3871 100644
--- a/crates/typst/src/math/stretch.rs
+++ b/crates/typst/src/math/stretch.rs
@@ -45,7 +45,7 @@ fn stretch_glyph(
.table
.variants
.and_then(|variants| {
- min_overlap = variants.min_connector_overlap.scaled(ctx);
+ min_overlap = variants.min_connector_overlap.scaled(ctx, base.font_size);
if horizontal {
variants.horizontal_constructions
} else {
@@ -106,12 +106,12 @@ fn assemble(
let mut growable = Abs::zero();
while let Some(part) = parts.next() {
- let mut advance = part.full_advance.scaled(ctx);
+ let mut advance = part.full_advance.scaled(ctx, base.font_size);
if let Some(next) = parts.peek() {
let max_overlap = part
.end_connector_length
.min(next.start_connector_length)
- .scaled(ctx);
+ .scaled(ctx, base.font_size);
advance -= max_overlap;
growable += max_overlap - min_overlap;
@@ -136,10 +136,12 @@ fn assemble(
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);
+ let mut advance = part.full_advance.scaled(ctx, base.font_size);
if let Some(next) = parts.peek() {
- let max_overlap =
- part.end_connector_length.min(next.start_connector_length).scaled(ctx);
+ let max_overlap = part
+ .end_connector_length
+ .min(next.start_connector_length)
+ .scaled(ctx, base.font_size);
advance -= max_overlap;
advance += ratio * (max_overlap - min_overlap);
}
@@ -156,7 +158,7 @@ fn assemble(
size = Size::new(full, height);
baseline = base.ascent;
} else {
- let axis = scaled!(ctx, axis_height);
+ let axis = ctx.constants.axis_height().scaled(ctx, base.font_size);
let width = selected.iter().map(|(f, _)| f.width).max().unwrap_or_default();
size = Size::new(width, full);
baseline = full / 2.0 + axis;
@@ -183,11 +185,11 @@ fn assemble(
c: base.c,
id: None,
frame,
- style: base.style,
font_size: base.font_size,
italics_correction: Abs::zero(),
accent_attach,
class: base.class,
+ math_size: base.math_size,
span: base.span,
limits: base.limits,
mid_stretched: None,
diff --git a/crates/typst/src/math/style.rs b/crates/typst/src/math/style.rs
index 0d3c06fa..595abf4a 100644
--- a/crates/typst/src/math/style.rs
+++ b/crates/typst/src/math/style.rs
@@ -1,11 +1,9 @@
-use unicode_math_class::MathClass;
+use comemo::Prehashed;
-use crate::diag::SourceResult;
-use crate::foundations::{
- elem, func, Cast, Content, NativeElement, Packed, Smart, StyleChain,
-};
-use crate::math::{LayoutMath, MathContext};
-use crate::syntax::Span;
+use crate::foundations::{func, Cast, Content, Smart, Style, StyleChain};
+use crate::layout::Abs;
+use crate::math::{EquationElem, MathContext};
+use crate::text::TextElem;
/// Bold font style in math.
///
@@ -14,12 +12,10 @@ use crate::syntax::Span;
/// ```
#[func]
pub fn bold(
- /// The call span of this function.
- span: Span,
/// The content to style.
body: Content,
) -> Content {
- MathStyleElem::new(body).with_bold(Some(true)).pack().spanned(span)
+ body.styled(EquationElem::set_bold(true))
}
/// Upright (non-italic) font style in math.
@@ -29,12 +25,10 @@ pub fn bold(
/// ```
#[func]
pub fn upright(
- /// The call span of this function.
- span: Span,
/// The content to style.
body: Content,
) -> Content {
- MathStyleElem::new(body).with_italic(Some(false)).pack().spanned(span)
+ body.styled(EquationElem::set_italic(Smart::Custom(false)))
}
/// Italic font style in math.
@@ -42,27 +36,21 @@ pub fn upright(
/// For roman letters and greek lowercase letters, this is already the default.
#[func]
pub fn italic(
- /// The call span of this function.
- span: Span,
/// The content to style.
body: Content,
) -> Content {
- MathStyleElem::new(body).with_italic(Some(true)).pack().spanned(span)
+ body.styled(EquationElem::set_italic(Smart::Custom(true)))
}
+
/// Serif (roman) font style in math.
///
/// This is already the default.
#[func]
pub fn serif(
- /// The call span of this function.
- span: Span,
/// The content to style.
body: Content,
) -> Content {
- MathStyleElem::new(body)
- .with_variant(Some(MathVariant::Serif))
- .pack()
- .spanned(span)
+ body.styled(EquationElem::set_variant(MathVariant::Serif))
}
/// Sans-serif font style in math.
@@ -72,15 +60,10 @@ pub fn serif(
/// ```
#[func(title = "Sans Serif")]
pub fn sans(
- /// The call span of this function.
- span: Span,
/// The content to style.
body: Content,
) -> Content {
- MathStyleElem::new(body)
- .with_variant(Some(MathVariant::Sans))
- .pack()
- .spanned(span)
+ body.styled(EquationElem::set_variant(MathVariant::Sans))
}
/// Calligraphic font style in math.
@@ -90,15 +73,10 @@ pub fn sans(
/// ```
#[func(title = "Calligraphic")]
pub fn cal(
- /// The call span of this function.
- span: Span,
/// The content to style.
body: Content,
) -> Content {
- MathStyleElem::new(body)
- .with_variant(Some(MathVariant::Cal))
- .pack()
- .spanned(span)
+ body.styled(EquationElem::set_variant(MathVariant::Cal))
}
/// Fraktur font style in math.
@@ -108,15 +86,10 @@ pub fn cal(
/// ```
#[func(title = "Fraktur")]
pub fn frak(
- /// The call span of this function.
- span: Span,
/// The content to style.
body: Content,
) -> Content {
- MathStyleElem::new(body)
- .with_variant(Some(MathVariant::Frak))
- .pack()
- .spanned(span)
+ body.styled(EquationElem::set_variant(MathVariant::Frak))
}
/// Monospace font style in math.
@@ -126,15 +99,10 @@ pub fn frak(
/// ```
#[func(title = "Monospace")]
pub fn mono(
- /// The call span of this function.
- span: Span,
/// The content to style.
body: Content,
) -> Content {
- MathStyleElem::new(body)
- .with_variant(Some(MathVariant::Mono))
- .pack()
- .spanned(span)
+ body.styled(EquationElem::set_variant(MathVariant::Mono))
}
/// Blackboard bold (double-struck) font style in math.
@@ -149,15 +117,10 @@ pub fn mono(
/// ```
#[func(title = "Blackboard Bold")]
pub fn bb(
- /// The call span of this function.
- span: Span,
/// The content to style.
body: Content,
) -> Content {
- MathStyleElem::new(body)
- .with_variant(Some(MathVariant::Bb))
- .pack()
- .spanned(span)
+ body.styled(EquationElem::set_variant(MathVariant::Bb))
}
/// Forced display style in math.
@@ -169,8 +132,6 @@ pub fn bb(
/// ```
#[func(title = "Display Size")]
pub fn display(
- /// The call span of this function.
- span: Span,
/// The content to size.
body: Content,
/// Whether to impose a height restriction for exponents, like regular sub-
@@ -179,11 +140,8 @@ pub fn display(
#[default(false)]
cramped: bool,
) -> Content {
- MathStyleElem::new(body)
- .with_size(Some(MathSize::Display))
- .with_cramped(Some(cramped))
- .pack()
- .spanned(span)
+ body.styled(EquationElem::set_size(MathSize::Display))
+ .styled(EquationElem::set_cramped(cramped))
}
/// Forced inline (text) style in math.
@@ -196,8 +154,6 @@ pub fn display(
/// ```
#[func(title = "Inline Size")]
pub fn inline(
- /// The call span of this function.
- span: Span,
/// The content to size.
body: Content,
/// Whether to impose a height restriction for exponents, like regular sub-
@@ -206,11 +162,8 @@ pub fn inline(
#[default(false)]
cramped: bool,
) -> Content {
- MathStyleElem::new(body)
- .with_size(Some(MathSize::Text))
- .with_cramped(Some(cramped))
- .pack()
- .spanned(span)
+ body.styled(EquationElem::set_size(MathSize::Text))
+ .styled(EquationElem::set_cramped(cramped))
}
/// Forced script style in math.
@@ -222,8 +175,6 @@ pub fn inline(
/// ```
#[func(title = "Script Size")]
pub fn script(
- /// The call span of this function.
- span: Span,
/// The content to size.
body: Content,
/// Whether to impose a height restriction for exponents, like regular sub-
@@ -232,11 +183,8 @@ pub fn script(
#[default(true)]
cramped: bool,
) -> Content {
- MathStyleElem::new(body)
- .with_size(Some(MathSize::Script))
- .with_cramped(Some(cramped))
- .pack()
- .spanned(span)
+ body.styled(EquationElem::set_size(MathSize::Script))
+ .styled(EquationElem::set_cramped(cramped))
}
/// Forced second script style in math.
@@ -249,8 +197,6 @@ pub fn script(
/// ```
#[func(title = "Script-Script Size")]
pub fn sscript(
- /// The call span of this function.
- span: Span,
/// The content to size.
body: Content,
/// Whether to impose a height restriction for exponents, like regular sub-
@@ -259,141 +205,8 @@ pub fn sscript(
#[default(true)]
cramped: bool,
) -> Content {
- MathStyleElem::new(body)
- .with_size(Some(MathSize::ScriptScript))
- .with_cramped(Some(cramped))
- .pack()
- .spanned(span)
-}
-
-/// A font variant in math.
-#[elem(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 Packed<MathStyleElem> {
- #[typst_macros::time(name = "math.style", span = self.span())]
- 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,
- /// The class of the element.
- pub class: Smart<MathClass>,
- /// 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 the given `class`.
- pub fn with_class(self, class: MathClass) -> Self {
- Self { class: Smart::Custom(class), ..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)
- }
+ body.styled(EquationElem::set_size(MathSize::ScriptScript))
+ .styled(EquationElem::set_cramped(cramped))
}
/// The size of elements in an equation.
@@ -412,7 +225,8 @@ pub enum MathSize {
}
impl MathSize {
- pub(super) fn factor(self, ctx: &MathContext) -> f64 {
+ /// The scaling factor.
+ pub fn factor(self, ctx: &MathContext) -> f64 {
match self {
Self::Display | Self::Text => 1.0,
Self::Script => percent!(ctx, script_percent_scale_down),
@@ -422,8 +236,9 @@ impl MathSize {
}
/// A mathematical style variant, as defined by Unicode.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Cast, Hash)]
+#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Cast, Hash)]
pub enum MathVariant {
+ #[default]
Serif,
Sans,
Cal,
@@ -432,21 +247,53 @@ pub enum MathVariant {
Bb,
}
-impl Default for MathVariant {
- fn default() -> Self {
- Self::Serif
- }
+/// Get the font size scaled with the `MathSize`.
+pub fn scaled_font_size(ctx: &MathContext, styles: StyleChain) -> Abs {
+ EquationElem::size_in(styles).factor(ctx) * TextElem::size_in(styles)
+}
+
+/// Styles something as cramped.
+pub fn style_cramped() -> Prehashed<Style> {
+ Prehashed::new(EquationElem::set_cramped(true))
+}
+
+/// The style for subscripts in the current style.
+pub fn style_for_subscript(styles: StyleChain) -> [Prehashed<Style>; 2] {
+ [style_for_superscript(styles), Prehashed::new(EquationElem::set_cramped(true))]
+}
+
+/// The style for superscripts in the current style.
+pub fn style_for_superscript(styles: StyleChain) -> Prehashed<Style> {
+ Prehashed::new(EquationElem::set_size(match EquationElem::size_in(styles) {
+ MathSize::Display | MathSize::Text => MathSize::Script,
+ MathSize::Script | MathSize::ScriptScript => MathSize::ScriptScript,
+ }))
+}
+
+/// The style for numerators in the current style.
+pub fn style_for_numerator(styles: StyleChain) -> Prehashed<Style> {
+ Prehashed::new(EquationElem::set_size(match EquationElem::size_in(styles) {
+ MathSize::Display => MathSize::Text,
+ MathSize::Text => MathSize::Script,
+ MathSize::Script | MathSize::ScriptScript => MathSize::ScriptScript,
+ }))
+}
+
+/// The style for denominators in the current style.
+pub fn style_for_denominator(styles: StyleChain) -> [Prehashed<Style>; 2] {
+ [style_for_numerator(styles), Prehashed::new(EquationElem::set_cramped(true))]
}
/// 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 {
+/// <https://www.w3.org/TR/mathml-core/#new-text-transform-mappings>
+/// <https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols>
+pub fn styled_char(styles: StyleChain, c: char) -> char {
use MathVariant::*;
- let MathStyle { variant, bold, .. } = style;
- let italic = style.italic.unwrap_or(matches!(
+ let variant = EquationElem::variant_in(styles);
+ let bold = EquationElem::bold_in(styles);
+ let italic = EquationElem::italic_in(styles).unwrap_or(matches!(
c,
'a'..='z' | 'ı' | 'ȷ' | 'A'..='Z' | 'α'..='ω' |
'∂' | 'ϵ' | 'ϑ' | 'ϰ' | 'ϕ' | 'ϱ' | 'ϖ'
diff --git a/crates/typst/src/math/underover.rs b/crates/typst/src/math/underover.rs
index 84edf036..84c9cbe1 100644
--- a/crates/typst/src/math/underover.rs
+++ b/crates/typst/src/math/underover.rs
@@ -1,11 +1,9 @@
-use unicode_math_class::MathClass;
-
use crate::diag::SourceResult;
-use crate::foundations::{elem, Content, Packed};
+use crate::foundations::{elem, Content, Packed, StyleChain};
use crate::layout::{Abs, Em, FixedAlignment, Frame, FrameItem, Point, Size};
use crate::math::{
- alignments, AlignmentResult, FrameFragment, GlyphFragment, LayoutMath, MathContext,
- MathRow, Scaled,
+ alignments, scaled_font_size, style_cramped, style_for_subscript, AlignmentResult,
+ FrameFragment, GlyphFragment, LayoutMath, MathContext, MathRow, Scaled,
};
use crate::syntax::Span;
use crate::text::TextElem;
@@ -34,8 +32,8 @@ pub struct UnderlineElem {
impl LayoutMath for Packed<UnderlineElem> {
#[typst_macros::time(name = "math.underline", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- layout_underoverline(ctx, self.body(), self.span(), LineKind::Under)
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
+ layout_underoverline(ctx, styles, self.body(), self.span(), LineKind::Under)
}
}
@@ -53,14 +51,15 @@ pub struct OverlineElem {
impl LayoutMath for Packed<OverlineElem> {
#[typst_macros::time(name = "math.overline", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- layout_underoverline(ctx, self.body(), self.span(), LineKind::Over)
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
+ layout_underoverline(ctx, styles, self.body(), self.span(), LineKind::Over)
}
}
/// layout under- or overlined content
fn layout_underoverline(
ctx: &mut MathContext,
+ styles: StyleChain,
body: &Content,
span: Span,
line: LineKind,
@@ -68,26 +67,25 @@ fn layout_underoverline(
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);
+ let sep = scaled!(ctx, styles, underbar_extra_descender);
+ bar_height = scaled!(ctx, styles, underbar_rule_thickness);
+ let gap = scaled!(ctx, styles, underbar_vertical_gap);
extra_height = sep + bar_height + gap;
- content = ctx.layout_fragment(body)?;
+ content = ctx.layout_fragment(body, styles)?;
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);
+ let sep = scaled!(ctx, styles, overbar_extra_ascender);
+ bar_height = scaled!(ctx, styles, overbar_rule_thickness);
+ let gap = scaled!(ctx, styles, overbar_vertical_gap);
extra_height = sep + bar_height + gap;
- ctx.style(ctx.style.with_cramped(true));
- content = ctx.layout_fragment(body)?;
- ctx.unstyle();
+ let cramped = style_cramped();
+ content = ctx.layout_fragment(body, styles.chain(&cramped))?;
line_pos = Point::with_y(sep + bar_height / 2.0);
content_pos = Point::with_y(extra_height);
@@ -99,7 +97,7 @@ fn layout_underoverline(
let height = content.height() + extra_height;
let size = Size::new(width, height);
- let content_class = content.class().unwrap_or(MathClass::Normal);
+ let content_class = content.class();
let mut frame = Frame::soft(size);
frame.set_baseline(baseline);
frame.push_frame(content_pos, content.into_frame());
@@ -107,7 +105,7 @@ fn layout_underoverline(
line_pos,
FrameItem::Shape(
Geometry::Line(Point::with_x(width)).stroked(FixedStroke {
- paint: TextElem::fill_in(ctx.styles()).as_decoration(),
+ paint: TextElem::fill_in(styles).as_decoration(),
thickness: bar_height,
..FixedStroke::default()
}),
@@ -115,7 +113,7 @@ fn layout_underoverline(
),
);
- ctx.push(FrameFragment::new(ctx, frame).with_class(content_class));
+ ctx.push(FrameFragment::new(ctx, styles, frame).with_class(content_class));
Ok(())
}
@@ -138,11 +136,12 @@ pub struct UnderbraceElem {
impl LayoutMath for Packed<UnderbraceElem> {
#[typst_macros::time(name = "math.underbrace", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
layout_underoverspreader(
ctx,
+ styles,
self.body(),
- &self.annotation(ctx.styles()),
+ &self.annotation(styles),
'⏟',
BRACE_GAP,
false,
@@ -169,11 +168,12 @@ pub struct OverbraceElem {
impl LayoutMath for Packed<OverbraceElem> {
#[typst_macros::time(name = "math.overbrace", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
layout_underoverspreader(
ctx,
+ styles,
self.body(),
- &self.annotation(ctx.styles()),
+ &self.annotation(styles),
'⏞',
BRACE_GAP,
true,
@@ -200,11 +200,12 @@ pub struct UnderbracketElem {
impl LayoutMath for Packed<UnderbracketElem> {
#[typst_macros::time(name = "math.underbrace", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
layout_underoverspreader(
ctx,
+ styles,
self.body(),
- &self.annotation(ctx.styles()),
+ &self.annotation(styles),
'⎵',
BRACKET_GAP,
false,
@@ -231,11 +232,12 @@ pub struct OverbracketElem {
impl LayoutMath for Packed<OverbracketElem> {
#[typst_macros::time(name = "math.overbracket", span = self.span())]
- fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
+ fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
layout_underoverspreader(
ctx,
+ styles,
self.body(),
- &self.annotation(ctx.styles()),
+ &self.annotation(styles),
'⎴',
BRACKET_GAP,
true,
@@ -245,8 +247,10 @@ impl LayoutMath for Packed<OverbracketElem> {
}
/// Layout an over- or underbrace-like object.
+#[allow(clippy::too_many_arguments)]
fn layout_underoverspreader(
ctx: &mut MathContext,
+ styles: StyleChain,
body: &Content,
annotation: &Option<Content>,
c: char,
@@ -254,26 +258,31 @@ fn layout_underoverspreader(
reverse: bool,
span: Span,
) -> SourceResult<()> {
- let gap = gap.scaled(ctx);
- let body = ctx.layout_row(body)?;
+ let font_size = scaled_font_size(ctx, styles);
+ let gap = gap.at(font_size);
+ let body = ctx.layout_row(body, styles)?;
let body_class = body.class();
- let body = body.into_fragment(ctx);
- let glyph = GlyphFragment::new(ctx, c, span);
+ let body = body.into_fragment(ctx, styles);
+ let glyph = GlyphFragment::new(ctx, styles, 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()
+
+ let (sup_style, sub_style);
+ let row_styles = if reverse {
+ sup_style = style_for_subscript(styles);
+ styles.chain(&sup_style)
} else {
- ctx.style.for_superscript()
- });
+ sub_style = style_for_subscript(styles);
+ styles.chain(&sub_style)
+ };
+
rows.extend(
annotation
.as_ref()
- .map(|annotation| ctx.layout_row(annotation))
+ .map(|annotation| ctx.layout_row(annotation, row_styles))
.transpose()?,
);
- ctx.unstyle();
let mut baseline = 0;
if reverse {
@@ -281,8 +290,8 @@ fn layout_underoverspreader(
baseline = rows.len() - 1;
}
- let frame = stack(ctx, rows, FixedAlignment::Center, gap, baseline);
- ctx.push(FrameFragment::new(ctx, frame).with_class(body_class));
+ let frame = stack(ctx, styles, rows, FixedAlignment::Center, gap, baseline);
+ ctx.push(FrameFragment::new(ctx, styles, frame).with_class(body_class));
Ok(())
}
@@ -293,6 +302,7 @@ fn layout_underoverspreader(
/// row for the whole frame.
pub(super) fn stack(
ctx: &MathContext,
+ styles: StyleChain,
rows: Vec<MathRow>,
align: FixedAlignment,
gap: Abs,
@@ -302,7 +312,7 @@ pub(super) fn stack(
let AlignmentResult { points, width } = alignments(&rows);
let rows: Vec<_> = rows
.into_iter()
- .map(|row| row.into_aligned_frame(ctx, &points, align))
+ .map(|row| row.into_aligned_frame(ctx, styles, &points, align))
.collect();
let mut y = Abs::zero();
diff --git a/tests/ref/math/class.png b/tests/ref/math/class.png
index a54dbd67..a4d6e86c 100644
--- a/tests/ref/math/class.png
+++ b/tests/ref/math/class.png
Binary files differ
diff --git a/tests/ref/math/equation-show.png b/tests/ref/math/equation-show.png
new file mode 100644
index 00000000..79a70dc0
--- /dev/null
+++ b/tests/ref/math/equation-show.png
Binary files differ
diff --git a/tests/typ/math/equation-show.typ b/tests/typ/math/equation-show.typ
new file mode 100644
index 00000000..9334c54e
--- /dev/null
+++ b/tests/typ/math/equation-show.typ
@@ -0,0 +1,7 @@
+// Test show rules on equations.
+
+---
+This is small: $sum_(i=0)^n$
+
+#show math.equation: math.display
+This is big: $sum_(i=0)^n$