diff options
| author | Laurenz <laurmaedje@gmail.com> | 2024-01-26 10:50:33 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-01-26 09:50:33 +0000 |
| commit | b09d6ae31c78463ad741644194c17ddd47c2bd56 (patch) | |
| tree | 99363b95467daa991a2aedf8727d5e02dd275fb5 | |
| parent | d8464a9a8174ac315ba4bff2e26b7d6abbcb9de6 (diff) | |
Refactor math styling to bring it closer to normal styling (#3262)
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 Binary files differindex a54dbd67..a4d6e86c 100644 --- a/tests/ref/math/class.png +++ b/tests/ref/math/class.png diff --git a/tests/ref/math/equation-show.png b/tests/ref/math/equation-show.png Binary files differnew file mode 100644 index 00000000..79a70dc0 --- /dev/null +++ b/tests/ref/math/equation-show.png 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$ |
