Refactor math styling to bring it closer to normal styling (#3262)

This commit is contained in:
Laurenz 2024-01-26 10:50:33 +01:00 committed by GitHub
parent d8464a9a81
commit b09d6ae31c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 698 additions and 754 deletions

View File

@ -445,17 +445,16 @@ impl<'a> StyleChain<'a> {
Self { head: &root.0, tail: None } 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 /// The resulting style chain contains styles from `local` as well as
/// `self`. The ones from `local` take precedence over the ones from /// `self`. The ones from `local` take precedence over the ones from
/// `self`. For folded properties `local` contributes the inner value. /// `self`. For folded properties `local` contributes the inner value.
pub fn chain<'b>(&'b self, local: &'b Styles) -> StyleChain<'b> { pub fn chain<'b, C>(&'b self, local: &'b C) -> StyleChain<'b>
if local.is_empty() { where
*self C: Chainable,
} else { {
StyleChain { head: &local.0, tail: Some(self) } Chainable::chain(local, self)
}
} }
/// Cast the first value for the given property in the chain, /// 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. /// An iterator over the entries in a style chain.
struct Entries<'a> { struct Entries<'a> {
inner: std::slice::Iter<'a, Prehashed<Style>>, inner: std::slice::Iter<'a, Prehashed<Style>>,

View File

@ -1,10 +1,11 @@
use unicode_math_class::MathClass;
use crate::diag::{bail, SourceResult}; 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::layout::{Em, Frame, Length, Point, Rel, Size};
use crate::math::{ use crate::math::{
FrameFragment, GlyphFragment, LayoutMath, MathContext, MathFragment, Scaled, style_cramped, FrameFragment, GlyphFragment, LayoutMath, MathContext, MathFragment,
Scaled,
}; };
use crate::symbols::Symbol; use crate::symbols::Symbol;
use crate::text::TextElem; use crate::text::TextElem;
@ -64,26 +65,25 @@ pub struct AccentElem {
impl LayoutMath for Packed<AccentElem> { impl LayoutMath for Packed<AccentElem> {
#[typst_macros::time(name = "math.accent", span = self.span())] #[typst_macros::time(name = "math.accent", span = self.span())]
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
ctx.style(ctx.style.with_cramped(true)); let cramped = style_cramped();
let base = ctx.layout_fragment(self.base())?; let base = ctx.layout_fragment(self.base(), styles.chain(&cramped))?;
ctx.unstyle();
// Preserve class to preserve automatic spacing. // 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 base_attach = base.accent_attach();
let width = self let width = self
.size(ctx.styles()) .size(styles)
.unwrap_or(Rel::one()) .unwrap_or(Rel::one())
.resolve(ctx.styles()) .resolve(styles)
.relative_to(base.width()); .relative_to(base.width());
// Forcing the accent to be at least as large as the base makes it too // Forcing the accent to be at least as large as the base makes it too
// wide in many case. // wide in many case.
let Accent(c) = self.accent(); let Accent(c) = self.accent();
let glyph = GlyphFragment::new(ctx, *c, self.span()); let glyph = GlyphFragment::new(ctx, styles, *c, self.span());
let short_fall = ACCENT_SHORT_FALL.scaled(ctx); let short_fall = ACCENT_SHORT_FALL.at(glyph.font_size);
let variant = glyph.stretch_horizontal(ctx, width, short_fall); let variant = glyph.stretch_horizontal(ctx, width, short_fall);
let accent = variant.frame; let accent = variant.frame;
let accent_attach = variant.accent_attach; 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 // 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 // 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. // 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 gap = -accent.descent() - base.height().min(accent_base_height);
let size = Size::new(base.width(), accent.height() + gap + base.height()); let size = Size::new(base.width(), accent.height() + gap + base.height());
let accent_pos = Point::with_x(base_attach - accent_attach); 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(accent_pos, accent);
frame.push_frame(base_pos, base.into_frame()); frame.push_frame(base_pos, base.into_frame());
ctx.push( ctx.push(
FrameFragment::new(ctx, frame) FrameFragment::new(ctx, styles, frame)
.with_class(base_class) .with_class(base_class)
.with_base_ascent(base_ascent) .with_base_ascent(base_ascent)
.with_italics_correction(base_italics_correction) .with_italics_correction(base_italics_correction)

View File

@ -1,5 +1,5 @@
use crate::diag::SourceResult; use crate::diag::SourceResult;
use crate::foundations::{elem, Packed}; use crate::foundations::{elem, Packed, StyleChain};
use crate::layout::Abs; use crate::layout::Abs;
use crate::math::{LayoutMath, MathContext, MathFragment, MathRow}; use crate::math::{LayoutMath, MathContext, MathFragment, MathRow};
@ -8,7 +8,7 @@ use crate::math::{LayoutMath, MathContext, MathFragment, MathRow};
pub struct AlignPointElem {} pub struct AlignPointElem {}
impl LayoutMath for Packed<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); ctx.push(MathFragment::Align);
Ok(()) Ok(())
} }

View File

@ -4,7 +4,8 @@ use crate::diag::SourceResult;
use crate::foundations::{elem, Content, Packed, StyleChain}; use crate::foundations::{elem, Content, Packed, StyleChain};
use crate::layout::{Abs, Frame, Point, Size}; use crate::layout::{Abs, Frame, Point, Size};
use crate::math::{ 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; use crate::text::TextElem;
@ -50,32 +51,32 @@ pub struct AttachElem {
impl LayoutMath for Packed<AttachElem> { impl LayoutMath for Packed<AttachElem> {
#[typst_macros::time(name = "math.attach", span = self.span())] #[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>; 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()
};
ctx.style(ctx.style.for_superscript()); let base = ctx.layout_fragment(self.base(), styles)?;
let tl = layout_attachment(ctx, AttachElem::tl)?;
let tr = layout_attachment(ctx, AttachElem::tr)?;
let t = layout_attachment(ctx, AttachElem::t)?;
ctx.unstyle();
ctx.style(ctx.style.for_subscript()); let sup_style = style_for_superscript(styles);
let bl = layout_attachment(ctx, AttachElem::bl)?; let tl = layout_attachment(ctx, styles.chain(&sup_style), AttachElem::tl)?;
let br = layout_attachment(ctx, AttachElem::br)?; let tr = layout_attachment(ctx, styles.chain(&sup_style), AttachElem::tr)?;
let b = layout_attachment(ctx, AttachElem::b)?; let t = layout_attachment(ctx, styles.chain(&sup_style), AttachElem::t)?;
ctx.unstyle();
let limits = base.limits().active(ctx); 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(styles);
let (t, tr) = if limits || tr.is_some() { (t, tr) } else { (None, t) }; 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) }; 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> { impl LayoutMath for Packed<PrimesElem> {
#[typst_macros::time(name = "math.primes", span = self.span())] #[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() { match *self.count() {
count @ 1..=4 => { count @ 1..=4 => {
let f = ctx.layout_fragment(&TextElem::packed(match count { let c = match count {
1 => '', 1 => '',
2 => '″', 2 => '″',
3 => '‴', 3 => '‴',
4 => '⁗', 4 => '⁗',
_ => unreachable!(), _ => unreachable!(),
}))?; };
let f = ctx.layout_fragment(&TextElem::packed(c), styles)?;
ctx.push(f); ctx.push(f);
} }
count => { count => {
// Custom amount of primes // 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 width = prime.width() * (count + 1) as f64 / 2.0;
let mut frame = Frame::soft(Size::new(width, prime.height())); let mut frame = Frame::soft(Size::new(width, prime.height()));
frame.set_baseline(prime.ascent()); frame.set_baseline(prime.ascent());
@ -123,7 +126,7 @@ impl LayoutMath for Packed<PrimesElem> {
prime.clone(), prime.clone(),
) )
} }
ctx.push(FrameFragment::new(ctx, frame)); ctx.push(FrameFragment::new(ctx, styles, frame));
} }
} }
Ok(()) Ok(())
@ -144,8 +147,8 @@ pub struct ScriptsElem {
impl LayoutMath for Packed<ScriptsElem> { impl LayoutMath for Packed<ScriptsElem> {
#[typst_macros::time(name = "math.scripts", span = self.span())] #[typst_macros::time(name = "math.scripts", span = self.span())]
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
let mut fragment = ctx.layout_fragment(self.body())?; let mut fragment = ctx.layout_fragment(self.body(), styles)?;
fragment.set_limits(Limits::Never); fragment.set_limits(Limits::Never);
ctx.push(fragment); ctx.push(fragment);
Ok(()) Ok(())
@ -173,13 +176,10 @@ pub struct LimitsElem {
impl LayoutMath for Packed<LimitsElem> { impl LayoutMath for Packed<LimitsElem> {
#[typst_macros::time(name = "math.limits", span = self.span())] #[typst_macros::time(name = "math.limits", span = self.span())]
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
let mut fragment = ctx.layout_fragment(self.body())?; let limits = if self.inline(styles) { Limits::Always } else { Limits::Display };
fragment.set_limits(if self.inline(ctx.styles()) { let mut fragment = ctx.layout_fragment(self.body(), styles)?;
Limits::Always fragment.set_limits(limits);
} else {
Limits::Display
});
ctx.push(fragment); ctx.push(fragment);
Ok(()) Ok(())
} }
@ -222,10 +222,10 @@ impl Limits {
} }
/// Whether limits should be displayed in this context /// Whether limits should be displayed in this context
pub fn active(&self, ctx: &MathContext) -> bool { pub fn active(&self, styles: StyleChain) -> bool {
match self { match self {
Self::Always => true, Self::Always => true,
Self::Display => ctx.style.size == MathSize::Display, Self::Display => EquationElem::size_in(styles) == MathSize::Display,
Self::Never => false, Self::Never => false,
} }
} }
@ -240,17 +240,18 @@ macro_rules! measure {
/// Layout the attachments. /// Layout the attachments.
fn layout_attachments( fn layout_attachments(
ctx: &mut MathContext, ctx: &mut MathContext,
styles: StyleChain,
base: MathFragment, base: MathFragment,
[tl, t, tr, bl, b, br]: [Option<MathFragment>; 6], [tl, t, tr, bl, b, br]: [Option<MathFragment>; 6],
) -> SourceResult<()> { ) -> SourceResult<()> {
let (shift_up, shift_down) = 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 sup_delta = Abs::zero();
let sub_delta = -base.italics_correction(); let sub_delta = -base.italics_correction();
let (base_width, base_ascent, base_descent) = let (base_width, base_ascent, base_descent) =
(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 let mut ascent = base_ascent
.max(shift_up + measure!(tr, ascent)) .max(shift_up + measure!(tr, ascent))
@ -269,9 +270,9 @@ fn layout_attachments(
let post_width_max = let post_width_max =
(sup_delta + measure!(tr, width)).max(sub_delta + measure!(br, width)); (sup_delta + measure!(tr, width)).max(sub_delta + measure!(br, width));
let (center_frame, base_offset) = attach_top_and_bottom(ctx, base, t, b); let (center_frame, base_offset) = attach_top_and_bottom(ctx, styles, base, t, b);
if [&tl, &bl, &tr, &br].iter().all(|&e| e.is_none()) { 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(()); return Ok(());
} }
@ -279,7 +280,10 @@ fn layout_attachments(
descent.set_max(center_frame.descent()); descent.set_max(center_frame.descent());
let mut frame = Frame::soft(Size::new( 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, ascent + descent,
)); ));
frame.set_baseline(ascent); frame.set_baseline(ascent);
@ -316,21 +320,22 @@ fn layout_attachments(
frame.push_frame(pos, br.into_frame()); 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(()) Ok(())
} }
fn attach_top_and_bottom( fn attach_top_and_bottom(
ctx: &mut MathContext, ctx: &mut MathContext,
styles: StyleChain,
base: MathFragment, base: MathFragment,
t: Option<MathFragment>, t: Option<MathFragment>,
b: Option<MathFragment>, b: Option<MathFragment>,
) -> (Frame, Abs) { ) -> (Frame, Abs) {
let upper_gap_min = scaled!(ctx, upper_limit_gap_min); let upper_gap_min = scaled!(ctx, styles, upper_limit_gap_min);
let upper_rise_min = scaled!(ctx, upper_limit_baseline_rise_min); let upper_rise_min = scaled!(ctx, styles, upper_limit_baseline_rise_min);
let lower_gap_min = scaled!(ctx, lower_limit_gap_min); let lower_gap_min = scaled!(ctx, styles, lower_limit_gap_min);
let lower_drop_min = scaled!(ctx, lower_limit_baseline_drop_min); let lower_drop_min = scaled!(ctx, styles, lower_limit_baseline_drop_min);
let mut base_offset = Abs::zero(); let mut base_offset = Abs::zero();
let mut width = base.width(); let mut width = base.width();
@ -372,22 +377,24 @@ fn attach_top_and_bottom(
fn compute_shifts_up_and_down( fn compute_shifts_up_and_down(
ctx: &MathContext, ctx: &MathContext,
styles: StyleChain,
base: &MathFragment, base: &MathFragment,
[tl, tr, bl, br]: [&Option<MathFragment>; 4], [tl, tr, bl, br]: [&Option<MathFragment>; 4],
) -> (Abs, Abs) { ) -> (Abs, Abs) {
let sup_shift_up = if ctx.style.cramped { let sup_shift_up = if EquationElem::cramped_in(styles) {
scaled!(ctx, superscript_shift_up_cramped) scaled!(ctx, styles, superscript_shift_up_cramped)
} else { } else {
scaled!(ctx, superscript_shift_up) scaled!(ctx, styles, superscript_shift_up)
}; };
let sup_bottom_min = scaled!(ctx, superscript_bottom_min); let sup_bottom_min = scaled!(ctx, styles, superscript_bottom_min);
let sup_bottom_max_with_sub = scaled!(ctx, superscript_bottom_max_with_subscript); let sup_bottom_max_with_sub =
let sup_drop_max = scaled!(ctx, superscript_baseline_drop_max); scaled!(ctx, styles, superscript_bottom_max_with_subscript);
let gap_min = scaled!(ctx, sub_superscript_gap_min); let sup_drop_max = scaled!(ctx, styles, superscript_baseline_drop_max);
let sub_shift_down = scaled!(ctx, subscript_shift_down); let gap_min = scaled!(ctx, styles, sub_superscript_gap_min);
let sub_top_max = scaled!(ctx, subscript_top_max); let sub_shift_down = scaled!(ctx, styles, subscript_shift_down);
let sub_drop_min = scaled!(ctx, subscript_baseline_drop_min); 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_up = Abs::zero();
let mut shift_down = Abs::zero(); let mut shift_down = Abs::zero();

View File

@ -1,7 +1,5 @@
use unicode_math_class::MathClass;
use crate::diag::{At, SourceResult}; 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::{ use crate::layout::{
Abs, Angle, Frame, FrameItem, Length, Point, Ratio, Rel, Size, Transform, Abs, Angle, Frame, FrameItem, Length, Point, Ratio, Rel, Size, Transform,
}; };
@ -107,17 +105,15 @@ pub struct CancelElem {
impl LayoutMath for Packed<CancelElem> { impl LayoutMath for Packed<CancelElem> {
#[typst_macros::time(name = "math.cancel", span = self.span())] #[typst_macros::time(name = "math.cancel", span = self.span())]
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
let body = ctx.layout_fragment(self.body())?; let body = ctx.layout_fragment(self.body(), styles)?;
// Preserve properties of body. // 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_italics = body.italics_correction();
let body_attach = body.accent_attach(); let body_attach = body.accent_attach();
let body_text_like = body.is_text_like(); let body_text_like = body.is_text_like();
let mut body = body.into_frame(); let mut body = body.into_frame();
let styles = ctx.styles();
let body_size = body.size(); let body_size = body.size();
let span = self.span(); let span = self.span();
let length = self.length(styles).resolve(styles); let length = self.length(styles).resolve(styles);
@ -155,7 +151,7 @@ impl LayoutMath for Packed<CancelElem> {
} }
ctx.push( ctx.push(
FrameFragment::new(ctx, body) FrameFragment::new(ctx, styles, body)
.with_class(body_class) .with_class(body_class)
.with_italics_correction(body_italics) .with_italics_correction(body_italics)
.with_accent_attach(body_attach) .with_accent_attach(body_attach)

View File

@ -1,8 +1,9 @@
use comemo::Prehashed;
use unicode_math_class::MathClass; use unicode_math_class::MathClass;
use crate::diag::SourceResult; use crate::diag::SourceResult;
use crate::foundations::{elem, Content, Packed}; use crate::foundations::{elem, Content, Packed, StyleChain};
use crate::math::{LayoutMath, Limits, MathContext}; use crate::math::{EquationElem, LayoutMath, Limits, MathContext};
/// Forced use of a certain math class. /// Forced use of a certain math class.
/// ///
@ -34,13 +35,12 @@ pub struct ClassElem {
impl LayoutMath for Packed<ClassElem> { impl LayoutMath for Packed<ClassElem> {
#[typst_macros::time(name = "math.class", span = self.span())] #[typst_macros::time(name = "math.class", span = self.span())]
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
ctx.style(ctx.style.with_class(*self.class())); let class = *self.class();
let mut fragment = ctx.layout_fragment(self.body())?; let style = Prehashed::new(EquationElem::set_class(Some(class)));
ctx.unstyle(); let mut fragment = ctx.layout_fragment(self.body(), styles.chain(&style))?;
fragment.set_class(class);
fragment.set_class(*self.class()); fragment.set_limits(Limits::for_class(class));
fragment.set_limits(Limits::for_class(*self.class()));
ctx.push(fragment); ctx.push(fragment);
Ok(()) Ok(())
} }

View File

@ -12,29 +12,30 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::diag::SourceResult; use crate::diag::SourceResult;
use crate::engine::Engine; 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::layout::{Abs, Axes, BoxElem, Em, Frame, LayoutMultiple, Regions, Size};
use crate::math::{ use crate::math::{
FrameFragment, GlyphFragment, LayoutMath, MathFragment, MathRow, MathSize, MathStyle, scaled_font_size, styled_char, EquationElem, FrameFragment, GlyphFragment,
MathVariant, THICK, LayoutMath, MathFragment, MathRow, MathSize, THICK,
}; };
use crate::model::ParElem; use crate::model::ParElem;
use crate::realize::realize;
use crate::syntax::{is_newline, Span}; use crate::syntax::{is_newline, Span};
use crate::text::{ use crate::text::{
features, variant, BottomEdge, BottomEdgeMetric, Font, FontStyle, FontWeight, features, BottomEdge, BottomEdgeMetric, Font, TextElem, TextSize, TopEdge,
TextElem, TextSize, TopEdge, TopEdgeMetric, TopEdgeMetric,
}; };
macro_rules! scaled { macro_rules! scaled {
($ctx:expr, text: $text:ident, display: $display:ident $(,)?) => { ($ctx:expr, $styles:expr, text: $text:ident, display: $display:ident $(,)?) => {
match $ctx.style.size { match $crate::math::EquationElem::size_in($styles) {
MathSize::Display => scaled!($ctx, $display), $crate::math::MathSize::Display => scaled!($ctx, $styles, $display),
_ => scaled!($ctx, $text), _ => scaled!($ctx, $styles, $text),
} }
}; };
($ctx:expr, $name:ident) => { ($ctx:expr, $styles:expr, $name:ident) => {
$ctx.constants.$name().scaled($ctx) $ctx.constants
.$name()
.scaled($ctx, $crate::math::scaled_font_size($ctx, $styles))
}; };
} }
@ -46,8 +47,10 @@ macro_rules! percent {
/// The context for math layout. /// The context for math layout.
pub struct MathContext<'a, 'b, 'v> { pub struct MathContext<'a, 'b, 'v> {
// External.
pub engine: &'v mut Engine<'b>, pub engine: &'v mut Engine<'b>,
pub regions: Regions<'static>, pub regions: Regions<'static>,
// Font-related.
pub font: &'a Font, pub font: &'a Font,
pub ttf: &'a ttf_parser::Face<'a>, pub ttf: &'a ttf_parser::Face<'a>,
pub table: ttf_parser::math::Table<'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 ssty_table: Option<ttf_parser::gsub::AlternateSubstitution<'a>>,
pub glyphwise_tables: Option<Vec<GlyphwiseSubsts<'a>>>, pub glyphwise_tables: Option<Vec<GlyphwiseSubsts<'a>>>,
pub space_width: Em, pub space_width: Em,
// Mutable.
pub fragments: Vec<MathFragment>, 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> { impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
@ -69,7 +68,6 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
styles: StyleChain<'a>, styles: StyleChain<'a>,
regions: Regions, regions: Regions,
font: &'a Font, font: &'a Font,
block: bool,
) -> Self { ) -> Self {
let math_table = font.ttf().tables().math.unwrap(); let math_table = font.ttf().tables().math.unwrap();
let gsub_table = font.ttf().tables().gsub; let gsub_table = font.ttf().tables().gsub;
@ -96,7 +94,6 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
.collect() .collect()
}); });
let size = TextElem::size_in(styles);
let ttf = font.ttf(); let ttf = font.ttf();
let space_width = ttf let space_width = ttf
.glyph_index(' ') .glyph_index(' ')
@ -104,7 +101,6 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
.map(|advance| font.to_em(advance)) .map(|advance| font.to_em(advance))
.unwrap_or(THICK); .unwrap_or(THICK);
let variant = variant(styles);
Self { Self {
engine, engine,
regions: Regions::one(regions.base(), Axes::splat(false)), regions: Regions::one(regions.base(), Axes::splat(false)),
@ -116,21 +112,6 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
glyphwise_tables, glyphwise_tables,
space_width, space_width,
fragments: vec![], 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); self.fragments.extend(fragments);
} }
pub fn layout_root(&mut self, elem: &dyn LayoutMath) -> SourceResult<MathRow> { pub fn layout_root(
let row = self.layout_fragments(elem)?; &mut self,
elem: &dyn LayoutMath,
styles: StyleChain,
) -> SourceResult<MathRow> {
let row = self.layout_fragments(elem, styles)?;
Ok(MathRow::new(row)) Ok(MathRow::new(row))
} }
pub fn layout_fragment( pub fn layout_fragment(
&mut self, &mut self,
elem: &dyn LayoutMath, elem: &dyn LayoutMath,
styles: StyleChain,
) -> SourceResult<MathFragment> { ) -> SourceResult<MathFragment> {
let row = self.layout_fragments(elem)?; let row = self.layout_fragments(elem, styles)?;
Ok(MathRow::new(row).into_fragment(self)) Ok(MathRow::new(row).into_fragment(self, styles))
} }
pub fn layout_fragments( pub fn layout_fragments(
&mut self, &mut self,
elem: &dyn LayoutMath, elem: &dyn LayoutMath,
styles: StyleChain,
) -> SourceResult<Vec<MathFragment>> { ) -> SourceResult<Vec<MathFragment>> {
let prev = std::mem::take(&mut self.fragments); 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)) Ok(std::mem::replace(&mut self.fragments, prev))
} }
pub fn layout_row(&mut self, elem: &dyn LayoutMath) -> SourceResult<MathRow> { pub fn layout_row(
let fragments = self.layout_fragments(elem)?; &mut self,
elem: &dyn LayoutMath,
styles: StyleChain,
) -> SourceResult<MathRow> {
let fragments = self.layout_fragments(elem, styles)?;
Ok(MathRow::new(fragments)) Ok(MathRow::new(fragments))
} }
pub fn layout_frame(&mut self, elem: &dyn LayoutMath) -> SourceResult<Frame> { pub fn layout_frame(
Ok(self.layout_fragment(elem)?.into_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> { pub fn layout_box(
boxed.layout(self.engine, self.outer.chain(&self.local), self.regions) &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 Ok(content
.layout(self.engine, self.outer.chain(&self.local), self.regions)? .layout(self.engine, styles.chain(&local), self.regions)?
.into_frame()) .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 text = elem.text();
let span = elem.span(); let span = elem.span();
let mut chars = text.chars(); let mut chars = text.chars();
let math_size = EquationElem::size_in(styles);
let fragment = if let Some(mut glyph) = chars let fragment = if let Some(mut glyph) = chars
.next() .next()
.filter(|_| chars.next().is_none()) .filter(|_| chars.next().is_none())
.map(|c| self.style.styled_char(c)) .map(|c| styled_char(styles, c))
.and_then(|c| GlyphFragment::try_new(self, c, span)) .and_then(|c| GlyphFragment::try_new(self, styles, c, span))
{ {
// A single letter that is available in the math font. // A single letter that is available in the math font.
match self.style.size { match math_size {
MathSize::Script => { MathSize::Script => {
glyph.make_scriptsize(self); 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 glyph.class == MathClass::Large {
if class == Some(MathClass::Large) { let mut variant = if math_size == MathSize::Display {
let mut variant = if self.style.size == MathSize::Display { let height = scaled!(self, styles, display_operator_min_height)
let height = scaled!(self, display_operator_min_height)
.max(SQRT_2 * glyph.height()); .max(SQRT_2 * glyph.height());
glyph.stretch_vertical(self, height, Abs::zero()) glyph.stretch_vertical(self, height, Abs::zero())
} else { } else {
@ -223,18 +236,23 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
// Numbers aren't that difficult. // Numbers aren't that difficult.
let mut fragments = vec![]; let mut fragments = vec![];
for c in text.chars() { for c in text.chars() {
let c = self.style.styled_char(c); let c = styled_char(styles, c);
fragments.push(GlyphFragment::new(self, c, span).into()); fragments.push(GlyphFragment::new(self, styles, c, span).into());
} }
let frame = MathRow::new(fragments).into_frame(self); let frame = MathRow::new(fragments).into_frame(self, styles);
FrameFragment::new(self, frame).with_text_like(true).into() FrameFragment::new(self, styles, frame).with_text_like(true).into()
} else { } 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. // Anything else is handled by Typst's standard text layout.
let mut style = self.style; let styles = styles.chain(&local);
if self.style.italic == Smart::Auto { let text: EcoString = text.chars().map(|c| styled_char(styles, c)).collect();
style = style.with_italic(false);
}
let text: EcoString = text.chars().map(|c| style.styled_char(c)).collect();
if text.contains(is_newline) { if text.contains(is_newline) {
let mut fragments = vec![]; let mut fragments = vec![];
for (i, piece) in text.split(is_newline).enumerate() { 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); fragments.push(MathFragment::Linebreak);
} }
if !piece.is_empty() { 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 mut frame = MathRow::new(fragments).into_frame(self, styles);
let axis = scaled!(self, axis_height); let axis = scaled!(self, styles, axis_height);
frame.set_baseline(frame.height() / 2.0 + axis); frame.set_baseline(frame.height() / 2.0 + axis);
FrameFragment::new(self, frame).into() FrameFragment::new(self, styles, frame).into()
} else { } else {
self.layout_complex_text(&text, span)?.into() self.layout_complex_text(&text, span, styles)?.into()
} }
}; };
Ok(fragment) Ok(fragment)
} }
pub fn layout_complex_text( fn layout_complex_text(
&mut self, &mut self,
text: &str, text: &str,
span: Span, span: Span,
styles: StyleChain,
) -> SourceResult<FrameFragment> { ) -> 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; // 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 // 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. // to extend as far as needed.
let span = elem.span(); let spaced = text.graphemes(true).nth(1).is_some();
let frame = Packed::new(ParElem::new(vec![Prehashed::new(elem)])) let text = TextElem::packed(text).spanned(span);
let par = ParElem::new(vec![Prehashed::new(text)]);
let frame = Packed::new(par)
.spanned(span) .spanned(span)
.layout( .layout(self.engine, styles, false, Size::splat(Abs::inf()), false)?
self.engine,
self.outer.chain(&self.local),
false,
Size::splat(Abs::inf()),
false,
)?
.into_frame(); .into_frame();
Ok(FrameFragment::new(self, frame) Ok(FrameFragment::new(self, styles, frame)
.with_class(MathClass::Alphabetic) .with_class(MathClass::Alphabetic)
.with_text_like(true) .with_text_like(true)
.with_spaced(spaced)) .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 { pub(super) trait Scaled {
fn scaled(self, ctx: &MathContext) -> Abs; fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs;
} }
impl Scaled for i16 { impl Scaled for i16 {
fn scaled(self, ctx: &MathContext) -> Abs { fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs {
ctx.font.to_em(self).scaled(ctx) ctx.font.to_em(self).at(font_size)
} }
} }
impl Scaled for u16 { impl Scaled for u16 {
fn scaled(self, ctx: &MathContext) -> Abs { fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs {
ctx.font.to_em(self).scaled(ctx) ctx.font.to_em(self).at(font_size)
}
}
impl Scaled for Em {
fn scaled(self, ctx: &MathContext) -> Abs {
self.at(ctx.size)
} }
} }
impl Scaled for MathValue<'_> { impl Scaled for MathValue<'_> {
fn scaled(self, ctx: &MathContext) -> Abs { fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs {
self.value.scaled(ctx) self.value.scaled(ctx, font_size)
} }
} }

View File

@ -1,5 +1,7 @@
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
use unicode_math_class::MathClass;
use crate::diag::{bail, SourceResult}; use crate::diag::{bail, SourceResult};
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{
@ -11,7 +13,7 @@ use crate::layout::{
Abs, AlignElem, Alignment, Axes, Dir, Em, FixedAlignment, Frame, LayoutMultiple, Abs, AlignElem, Alignment, Axes, Dir, Em, FixedAlignment, Frame, LayoutMultiple,
LayoutSingle, Point, Regions, Size, 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::model::{Numbering, Outlinable, ParElem, Refable, Supplement};
use crate::syntax::Span; use crate::syntax::Span;
use crate::text::{ use crate::text::{
@ -94,6 +96,33 @@ pub struct EquationElem {
/// The contents of the equation. /// The contents of the equation.
#[required] #[required]
pub body: Content, 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> { impl Synthesize for Packed<EquationElem> {
@ -124,6 +153,7 @@ impl Finalize for Packed<EquationElem> {
let mut realized = realized; let mut realized = realized;
if self.block(style) { if self.block(style) {
realized = realized.styled(AlignElem::set_alignment(Alignment::CENTER)); realized = realized.styled(AlignElem::set_alignment(Alignment::CENTER));
realized = realized.styled(EquationElem::set_size(MathSize::Display));
} }
realized realized
.styled(TextElem::set_weight(FontWeight::from_number(450))) .styled(TextElem::set_weight(FontWeight::from_number(450)))
@ -162,19 +192,19 @@ impl Packed<EquationElem> {
// Find a math font. // Find a math font.
let font = find_math_font(engine, styles, self.span())?; let font = find_math_font(engine, styles, self.span())?;
let mut ctx = MathContext::new(engine, styles, regions, &font, false); let mut ctx = MathContext::new(engine, styles, regions, &font);
let rows = ctx.layout_root(self)?; let rows = ctx.layout_root(self, styles)?;
let mut items = if rows.row_count() == 1 { let mut items = if rows.row_count() == 1 {
rows.into_par_items() rows.into_par_items()
} else { } 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 { for item in &mut items {
let MathParItem::Frame(frame) = item else { continue }; 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 slack = ParElem::leading_in(styles) * 0.7;
let top_edge = TextElem::top_edge_in(styles).resolve(font_size, &font, None); let top_edge = TextElem::top_edge_in(styles).resolve(font_size, &font, None);
let bottom_edge = let bottom_edge =
@ -205,8 +235,8 @@ impl LayoutSingle for Packed<EquationElem> {
// Find a math font. // Find a math font.
let font = find_math_font(engine, styles, self.span())?; let font = find_math_font(engine, styles, self.span())?;
let mut ctx = MathContext::new(engine, styles, regions, &font, true); let mut ctx = MathContext::new(engine, styles, regions, &font);
let mut frame = ctx.layout_frame(self)?; let mut frame = ctx.layout_frame(self, styles)?;
if let Some(numbering) = (**self).numbering(styles) { if let Some(numbering) = (**self).numbering(styles) {
let pod = Regions::one(regions.base(), Axes::splat(false)); let pod = Regions::one(regions.base(), Axes::splat(false));
@ -341,8 +371,8 @@ impl Outlinable for Packed<EquationElem> {
impl LayoutMath for Packed<EquationElem> { impl LayoutMath for Packed<EquationElem> {
#[typst_macros::time(name = "math.equation", span = self.span())] #[typst_macros::time(name = "math.equation", span = self.span())]
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
self.body().layout_math(ctx) self.body().layout_math(ctx, styles)
} }
} }

View File

@ -1,9 +1,9 @@
use crate::diag::{bail, SourceResult}; 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::layout::{Em, Frame, FrameItem, Point, Size};
use crate::math::{ use crate::math::{
FrameFragment, GlyphFragment, LayoutMath, MathContext, MathSize, Scaled, scaled_font_size, style_for_denominator, style_for_numerator, FrameFragment,
DELIM_SHORT_FALL, GlyphFragment, LayoutMath, MathContext, Scaled, DELIM_SHORT_FALL,
}; };
use crate::syntax::{Span, Spanned}; use crate::syntax::{Span, Spanned};
use crate::text::TextElem; use crate::text::TextElem;
@ -37,8 +37,15 @@ pub struct FracElem {
impl LayoutMath for Packed<FracElem> { impl LayoutMath for Packed<FracElem> {
#[typst_macros::time(name = "math.frac", span = self.span())] #[typst_macros::time(name = "math.frac", span = self.span())]
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
layout(ctx, self.num(), std::slice::from_ref(self.denom()), false, self.span()) 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> { impl LayoutMath for Packed<BinomElem> {
#[typst_macros::time(name = "math.binom", span = self.span())] #[typst_macros::time(name = "math.binom", span = self.span())]
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
layout(ctx, self.upper(), self.lower(), true, self.span()) layout(ctx, styles, self.upper(), self.lower(), true, self.span())
} }
} }
/// Layout a fraction or binomial. /// Layout a fraction or binomial.
fn layout( fn layout(
ctx: &mut MathContext, ctx: &mut MathContext,
styles: StyleChain,
num: &Content, num: &Content,
denom: &[Content], denom: &[Content],
binom: bool, binom: bool,
span: Span, span: Span,
) -> SourceResult<()> { ) -> SourceResult<()> {
let short_fall = DELIM_SHORT_FALL.scaled(ctx); let font_size = scaled_font_size(ctx, styles);
let axis = scaled!(ctx, axis_height); let short_fall = DELIM_SHORT_FALL.at(font_size);
let thickness = scaled!(ctx, fraction_rule_thickness); let axis = scaled!(ctx, styles, axis_height);
let thickness = scaled!(ctx, styles, fraction_rule_thickness);
let shift_up = scaled!( let shift_up = scaled!(
ctx, ctx, styles,
text: fraction_numerator_shift_up, text: fraction_numerator_shift_up,
display: fraction_numerator_display_style_shift_up, display: fraction_numerator_display_style_shift_up,
); );
let shift_down = scaled!( let shift_down = scaled!(
ctx, ctx, styles,
text: fraction_denominator_shift_down, text: fraction_denominator_shift_down,
display: fraction_denominator_display_style_shift_down, display: fraction_denominator_display_style_shift_down,
); );
let num_min = scaled!( let num_min = scaled!(
ctx, ctx, styles,
text: fraction_numerator_gap_min, text: fraction_numerator_gap_min,
display: fraction_num_display_style_gap_min, display: fraction_num_display_style_gap_min,
); );
let denom_min = scaled!( let denom_min = scaled!(
ctx, ctx, styles,
text: fraction_denominator_gap_min, text: fraction_denominator_gap_min,
display: fraction_denom_display_style_gap_min, display: fraction_denom_display_style_gap_min,
); );
ctx.style(ctx.style.for_numerator()); let num_style = style_for_numerator(styles);
let num = ctx.layout_frame(num)?; let num = ctx.layout_frame(num, styles.chain(&num_style))?;
ctx.unstyle();
ctx.style(ctx.style.for_denominator()); let denom_style = style_for_denominator(styles);
let denom = ctx.layout_frame(&Content::sequence( let denom = ctx.layout_frame(
// Add a comma between each element. &Content::sequence(
denom.iter().flat_map(|a| [TextElem::packed(','), a.clone()]).skip(1), // Add a comma between each element.
))?; denom.iter().flat_map(|a| [TextElem::packed(','), a.clone()]).skip(1),
ctx.unstyle(); ),
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 num_gap = (shift_up - axis - num.descent()).max(num_min + thickness / 2.0);
let denom_gap = (shift_down + axis - denom.ascent()).max(denom_min + thickness / 2.0); let 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); frame.push_frame(denom_pos, denom);
if binom { if binom {
let mut left = let mut left = GlyphFragment::new(ctx, styles, '(', span)
GlyphFragment::new(ctx, '(', span).stretch_vertical(ctx, height, short_fall); .stretch_vertical(ctx, height, short_fall);
left.center_on_axis(ctx); left.center_on_axis(ctx);
ctx.push(left); ctx.push(left);
ctx.push(FrameFragment::new(ctx, frame)); ctx.push(FrameFragment::new(ctx, styles, frame));
let mut right = let mut right = GlyphFragment::new(ctx, styles, ')', span)
GlyphFragment::new(ctx, ')', span).stretch_vertical(ctx, height, short_fall); .stretch_vertical(ctx, height, short_fall);
right.center_on_axis(ctx); right.center_on_axis(ctx);
ctx.push(right); ctx.push(right);
} else { } else {
@ -154,14 +164,14 @@ fn layout(
FrameItem::Shape( FrameItem::Shape(
Geometry::Line(Point::with_x(line_width)).stroked( Geometry::Line(Point::with_x(line_width)).stroked(
FixedStroke::from_pair( FixedStroke::from_pair(
TextElem::fill_in(ctx.styles()).as_decoration(), TextElem::fill_in(styles).as_decoration(),
thickness, thickness,
), ),
), ),
span, span,
), ),
); );
ctx.push(FrameFragment::new(ctx, frame)); ctx.push(FrameFragment::new(ctx, styles, frame));
} }
Ok(()) Ok(())

View File

@ -5,10 +5,12 @@ use ttf_parser::gsub::AlternateSet;
use ttf_parser::{GlyphId, Rect}; use ttf_parser::{GlyphId, Rect};
use unicode_math_class::MathClass; use unicode_math_class::MathClass;
use crate::foundations::Smart; use crate::foundations::StyleChain;
use crate::introspection::{Meta, MetaElem}; use crate::introspection::{Meta, MetaElem};
use crate::layout::{Abs, Corner, Em, Frame, FrameItem, Point, Size}; 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::syntax::Span;
use crate::text::{Font, Glyph, Lang, TextElem, TextItem}; use crate::text::{Font, Glyph, Lang, TextElem, TextItem};
use crate::visualize::Paint; use crate::visualize::Paint;
@ -67,20 +69,23 @@ impl MathFragment {
} }
} }
pub fn class(&self) -> Option<MathClass> { pub fn class(&self) -> MathClass {
self.style().and_then(|style| style.class.as_custom()).or(match self { match self {
Self::Glyph(glyph) => glyph.class, Self::Glyph(glyph) => glyph.class,
Self::Variant(variant) => variant.class, Self::Variant(variant) => variant.class,
Self::Frame(fragment) => Some(fragment.class), Self::Frame(fragment) => fragment.class,
_ => None, 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 { match self {
Self::Glyph(glyph) => Some(glyph.style), Self::Glyph(glyph) => Some(glyph.math_size),
Self::Variant(variant) => Some(variant.style), Self::Variant(variant) => Some(variant.math_size),
Self::Frame(fragment) => Some(fragment.style), Self::Frame(fragment) => Some(fragment.math_size),
_ => None, _ => None,
} }
} }
@ -95,27 +100,10 @@ impl MathFragment {
} }
pub fn set_class(&mut self, class: MathClass) { 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 { match self {
Self::Glyph(glyph) => { Self::Glyph(glyph) => glyph.class = class,
glyph.class = Some(class); Self::Variant(variant) => variant.class = class,
set_style_class!(glyph); Self::Frame(fragment) => fragment.class = class,
}
Self::Variant(variant) => {
variant.class = Some(class);
set_style_class!(variant);
}
Self::Frame(fragment) => {
fragment.class = class;
set_style_class!(fragment);
}
_ => {} _ => {}
} }
} }
@ -130,21 +118,22 @@ impl MathFragment {
} }
pub fn is_spaced(&self) -> bool { pub fn is_spaced(&self) -> bool {
match self { self.class() == MathClass::Fence
MathFragment::Frame(frame) => { || match self {
match self.style().and_then(|style| style.class.as_custom()) { MathFragment::Frame(frame) => {
Some(MathClass::Fence) => true, frame.spaced
Some(_) => false, && matches!(
None => frame.spaced, frame.class,
MathClass::Normal | MathClass::Alphabetic
)
} }
_ => false,
} }
_ => self.class() == Some(MathClass::Fence),
}
} }
pub fn is_text_like(&self) -> bool { pub fn is_text_like(&self) -> bool {
match self { 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, MathFragment::Frame(frame) => frame.text_like,
_ => false, _ => false,
} }
@ -224,43 +213,57 @@ pub struct GlyphFragment {
pub descent: Abs, pub descent: Abs,
pub italics_correction: Abs, pub italics_correction: Abs,
pub accent_attach: Abs, pub accent_attach: Abs,
pub style: MathStyle,
pub font_size: Abs, pub font_size: Abs,
pub class: Option<MathClass>, pub class: MathClass,
pub math_size: MathSize,
pub span: Span, pub span: Span,
pub meta: SmallVec<[Meta; 1]>, pub meta: SmallVec<[Meta; 1]>,
pub limits: Limits, pub limits: Limits,
} }
impl GlyphFragment { 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 = ctx.ttf.glyph_index(c).unwrap_or_default();
let id = Self::adjust_glyph_index(ctx, id); 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> { pub fn try_new(
let c = ctx.style.styled_char(c); 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 = ctx.ttf.glyph_index(c)?;
let id = Self::adjust_glyph_index(ctx, id); 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, c: char, id: GlyphId, span: Span) -> Self { pub fn with_id(
let class = match c { ctx: &MathContext,
':' => Some(MathClass::Relation), styles: StyleChain,
'.' | '/' | '⋯' | '⋱' | '⋰' | '⋮' => Some(MathClass::Normal), c: char,
_ => unicode_math_class::class(c), 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);
let mut fragment = Self { let mut fragment = Self {
id, id,
c, c,
font: ctx.font.clone(), font: ctx.font.clone(),
lang: TextElem::lang_in(ctx.styles()), lang: TextElem::lang_in(styles),
fill: TextElem::fill_in(ctx.styles()).as_decoration(), fill: TextElem::fill_in(styles).as_decoration(),
shift: TextElem::baseline_in(ctx.styles()), shift: TextElem::baseline_in(styles),
style: ctx.style, font_size: scaled_font_size(ctx, styles),
font_size: ctx.size, math_size: EquationElem::size_in(styles),
width: Abs::zero(), width: Abs::zero(),
ascent: Abs::zero(), ascent: Abs::zero(),
descent: Abs::zero(), descent: Abs::zero(),
@ -269,7 +272,7 @@ impl GlyphFragment {
accent_attach: Abs::zero(), accent_attach: Abs::zero(),
class, class,
span, span,
meta: MetaElem::data_in(ctx.styles()), meta: MetaElem::data_in(styles),
}; };
fragment.set_id(ctx, id); fragment.set_id(ctx, id);
fragment fragment
@ -288,7 +291,7 @@ impl GlyphFragment {
/// styles. This is used to replace the glyph with a stretch variant. /// styles. This is used to replace the glyph with a stretch variant.
pub fn set_id(&mut self, ctx: &MathContext, id: GlyphId) { pub fn set_id(&mut self, ctx: &MathContext, id: GlyphId) {
let advance = ctx.ttf.glyph_hor_advance(id).unwrap_or_default(); 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 { let bbox = ctx.ttf.glyph_bounding_box(id).unwrap_or(Rect {
x_min: 0, x_min: 0,
y_min: 0, y_min: 0,
@ -296,8 +299,9 @@ impl GlyphFragment {
y_max: 0, y_max: 0,
}); });
let mut width = advance.scaled(ctx); let mut width = advance.scaled(ctx, self.font_size);
let accent_attach = accent_attach(ctx, id).unwrap_or((width + italics) / 2.0); let accent_attach =
accent_attach(ctx, id, self.font_size).unwrap_or((width + italics) / 2.0);
if !is_extended_shape(ctx, id) { if !is_extended_shape(ctx, id) {
width += italics; width += italics;
@ -305,8 +309,8 @@ impl GlyphFragment {
self.id = id; self.id = id;
self.width = width; self.width = width;
self.ascent = bbox.y_max.scaled(ctx); self.ascent = bbox.y_max.scaled(ctx, self.font_size);
self.descent = -bbox.y_min.scaled(ctx); self.descent = -bbox.y_min.scaled(ctx, self.font_size);
self.italics_correction = italics; self.italics_correction = italics;
self.accent_attach = accent_attach; self.accent_attach = accent_attach;
} }
@ -319,11 +323,11 @@ impl GlyphFragment {
VariantFragment { VariantFragment {
c: self.c, c: self.c,
id: Some(self.id), id: Some(self.id),
style: self.style,
font_size: self.font_size, font_size: self.font_size,
italics_correction: self.italics_correction, italics_correction: self.italics_correction,
accent_attach: self.accent_attach, accent_attach: self.accent_attach,
class: self.class, class: self.class,
math_size: self.math_size,
span: self.span, span: self.span,
limits: self.limits, limits: self.limits,
frame: self.into_frame(), frame: self.into_frame(),
@ -388,9 +392,9 @@ pub struct VariantFragment {
pub italics_correction: Abs, pub italics_correction: Abs,
pub accent_attach: Abs, pub accent_attach: Abs,
pub frame: Frame, pub frame: Frame,
pub style: MathStyle,
pub font_size: Abs, pub font_size: Abs,
pub class: Option<MathClass>, pub class: MathClass,
pub math_size: MathSize,
pub span: Span, pub span: Span,
pub limits: Limits, pub limits: Limits,
pub mid_stretched: Option<bool>, pub mid_stretched: Option<bool>,
@ -401,7 +405,8 @@ impl VariantFragment {
/// on the axis. /// on the axis.
pub fn center_on_axis(&mut self, ctx: &MathContext) { pub fn center_on_axis(&mut self, ctx: &MathContext) {
let h = self.frame.height(); 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)] #[derive(Debug, Clone)]
pub struct FrameFragment { pub struct FrameFragment {
pub frame: Frame, pub frame: Frame,
pub style: MathStyle,
pub font_size: Abs, pub font_size: Abs,
pub class: MathClass, pub class: MathClass,
pub math_size: MathSize,
pub limits: Limits, pub limits: Limits,
pub spaced: bool, pub spaced: bool,
pub base_ascent: Abs, pub base_ascent: Abs,
@ -426,15 +431,15 @@ pub struct FrameFragment {
} }
impl 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 base_ascent = frame.ascent();
let accent_attach = frame.width() / 2.0; let accent_attach = frame.width() / 2.0;
frame.meta(ctx.styles(), false); frame.meta(styles, false);
Self { Self {
frame, frame,
font_size: ctx.size, font_size: scaled_font_size(ctx, styles),
style: ctx.style, class: EquationElem::class_in(styles).unwrap_or(MathClass::Normal),
class: MathClass::Normal, math_size: EquationElem::size_in(styles),
limits: Limits::Never, limits: Limits::Never,
spaced: false, spaced: false,
base_ascent, base_ascent,
@ -480,13 +485,25 @@ pub struct SpacingFragment {
} }
/// Look up the italics correction for a glyph. /// Look up the italics correction for a glyph.
fn italics_correction(ctx: &MathContext, id: GlyphId) -> Option<Abs> { fn italics_correction(ctx: &MathContext, id: GlyphId, font_size: Abs) -> Option<Abs> {
Some(ctx.table.glyph_info?.italic_corrections?.get(id)?.scaled(ctx)) Some(
ctx.table
.glyph_info?
.italic_corrections?
.get(id)?
.scaled(ctx, font_size),
)
} }
/// Loop up the top accent attachment position for a glyph. /// Loop up the top accent attachment position for a glyph.
fn accent_attach(ctx: &MathContext, id: GlyphId) -> Option<Abs> { fn accent_attach(ctx: &MathContext, id: GlyphId, font_size: Abs) -> Option<Abs> {
Some(ctx.table.glyph_info?.top_accent_attachments?.get(id)?.scaled(ctx)) Some(
ctx.table
.glyph_info?
.top_accent_attachments?
.get(id)?
.scaled(ctx, font_size),
)
} }
/// Look up the script/scriptscript alternates for a glyph /// Look up the script/scriptscript alternates for a glyph
@ -515,6 +532,7 @@ fn is_extended_shape(ctx: &MathContext, id: GlyphId) -> bool {
#[allow(unused)] #[allow(unused)]
fn kern_at_height( fn kern_at_height(
ctx: &MathContext, ctx: &MathContext,
font_size: Abs,
id: GlyphId, id: GlyphId,
corner: Corner, corner: Corner,
height: Abs, height: Abs,
@ -528,9 +546,9 @@ fn kern_at_height(
}?; }?;
let mut i = 0; 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; i += 1;
} }
Some(kern.kern(i)?.scaled(ctx)) Some(kern.kern(i)?.scaled(ctx, font_size))
} }

View File

@ -1,7 +1,9 @@
use unicode_math_class::MathClass; use unicode_math_class::MathClass;
use crate::diag::SourceResult; 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::layout::{Abs, Em, Length, Rel};
use crate::math::{ use crate::math::{
GlyphFragment, LayoutMath, MathContext, MathFragment, Scaled, SpacingFragment, GlyphFragment, LayoutMath, MathContext, MathFragment, Scaled, SpacingFragment,
@ -37,16 +39,16 @@ pub struct LrElem {
impl LayoutMath for Packed<LrElem> { impl LayoutMath for Packed<LrElem> {
#[typst_macros::time(name = "math.lr", span = self.span())] #[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(); let mut body = self.body();
if let Some(elem) = body.to_packed::<LrElem>() { if let Some(elem) = body.to_packed::<LrElem>() {
if elem.size(ctx.styles()).is_auto() { if elem.size(styles).is_auto() {
body = elem.body(); body = elem.body();
} }
} }
let mut fragments = ctx.layout_fragments(body)?; let mut fragments = ctx.layout_fragments(body, styles)?;
let axis = scaled!(ctx, axis_height); let axis = scaled!(ctx, styles, axis_height);
let max_extent = fragments let max_extent = fragments
.iter() .iter()
.map(|fragment| (fragment.ascent() - axis).max(fragment.descent() + axis)) .map(|fragment| (fragment.ascent() - axis).max(fragment.descent() + axis))
@ -54,17 +56,17 @@ impl LayoutMath for Packed<LrElem> {
.unwrap_or_default(); .unwrap_or_default();
let height = self let height = self
.size(ctx.styles()) .size(styles)
.unwrap_or(Rel::one()) .unwrap_or(Rel::one())
.resolve(ctx.styles()) .resolve(styles)
.relative_to(2.0 * max_extent); .relative_to(2.0 * max_extent);
// Scale up fragments at both ends. // Scale up fragments at both ends.
match fragments.as_mut_slice() { match fragments.as_mut_slice() {
[one] => scale(ctx, one, height, None), [one] => scale(ctx, styles, one, height, None),
[first, .., last] => { [first, .., last] => {
scale(ctx, first, height, Some(MathClass::Opening)); scale(ctx, styles, first, height, Some(MathClass::Opening));
scale(ctx, last, height, Some(MathClass::Closing)); 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 let MathFragment::Variant(ref mut variant) = fragment {
if variant.mid_stretched == Some(false) { if variant.mid_stretched == Some(false) {
variant.mid_stretched = Some(true); 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> { impl LayoutMath for Packed<MidElem> {
#[typst_macros::time(name = "math.mid", span = self.span())] #[typst_macros::time(name = "math.mid", span = self.span())]
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
let mut fragments = ctx.layout_fragments(self.body())?; let mut fragments = ctx.layout_fragments(self.body(), styles)?;
for fragment in &mut fragments { for fragment in &mut fragments {
match fragment { match fragment {
@ -137,23 +139,24 @@ impl LayoutMath for Packed<MidElem> {
/// Scale a math fragment to a height. /// Scale a math fragment to a height.
fn scale( fn scale(
ctx: &mut MathContext, ctx: &mut MathContext,
styles: StyleChain,
fragment: &mut MathFragment, fragment: &mut MathFragment,
height: Abs, height: Abs,
apply: Option<MathClass>, apply: Option<MathClass>,
) { ) {
if matches!( if matches!(
fragment.class(), fragment.class(),
Some(MathClass::Opening | MathClass::Closing | MathClass::Fence) MathClass::Opening | MathClass::Closing | MathClass::Fence
) { ) {
let glyph = match fragment { let glyph = match fragment {
MathFragment::Glyph(glyph) => glyph.clone(), MathFragment::Glyph(glyph) => glyph.clone(),
MathFragment::Variant(variant) => { MathFragment::Variant(variant) => {
GlyphFragment::new(ctx, variant.c, variant.span) GlyphFragment::new(ctx, styles, variant.c, variant.span)
} }
_ => return, _ => 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); let mut stretched = glyph.stretch_vertical(ctx, height, short_fall);
stretched.center_on_axis(ctx); stretched.center_on_axis(ctx);

View File

@ -9,8 +9,8 @@ use crate::layout::{
Abs, Axes, Em, FixedAlignment, Frame, FrameItem, Length, Point, Ratio, Rel, Size, Abs, Axes, Em, FixedAlignment, Frame, FrameItem, Length, Point, Ratio, Rel, Size,
}; };
use crate::math::{ use crate::math::{
alignments, stack, AlignmentResult, FrameFragment, GlyphFragment, LayoutMath, alignments, scaled_font_size, stack, style_for_denominator, AlignmentResult,
MathContext, Scaled, DELIM_SHORT_FALL, FrameFragment, GlyphFragment, LayoutMath, MathContext, Scaled, DELIM_SHORT_FALL,
}; };
use crate::syntax::{Span, Spanned}; use crate::syntax::{Span, Spanned};
use crate::text::TextElem; use crate::text::TextElem;
@ -59,16 +59,19 @@ pub struct VecElem {
impl LayoutMath for Packed<VecElem> { impl LayoutMath for Packed<VecElem> {
#[typst_macros::time(name = "math.vec", span = self.span())] #[typst_macros::time(name = "math.vec", span = self.span())]
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
let delim = self.delim(ctx.styles()); let delim = self.delim(styles);
let frame = layout_vec_body( let frame = layout_vec_body(
ctx, ctx,
styles,
self.children(), self.children(),
FixedAlignment::Center, FixedAlignment::Center,
self.gap(ctx.styles()), self.gap(styles),
)?; )?;
layout_delimiters( layout_delimiters(
ctx, ctx,
styles,
frame, frame,
delim.map(Delimiter::open), delim.map(Delimiter::open),
delim.map(Delimiter::close), delim.map(Delimiter::close),
@ -212,8 +215,8 @@ pub struct MatElem {
impl LayoutMath for Packed<MatElem> { impl LayoutMath for Packed<MatElem> {
#[typst_macros::time(name = "math.mat", span = self.span())] #[typst_macros::time(name = "math.mat", span = self.span())]
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
let augment = self.augment(ctx.styles()); let augment = self.augment(styles);
let rows = self.rows(); let rows = self.rows();
if let Some(aug) = &augment { 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( let frame = layout_mat_body(
ctx, ctx,
styles,
rows, rows,
augment, 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(), self.span(),
)?; )?;
layout_delimiters( layout_delimiters(
ctx, ctx,
styles,
frame, frame,
delim.map(Delimiter::open), delim.map(Delimiter::open),
delim.map(Delimiter::close), delim.map(Delimiter::close),
@ -311,22 +316,23 @@ pub struct CasesElem {
impl LayoutMath for Packed<CasesElem> { impl LayoutMath for Packed<CasesElem> {
#[typst_macros::time(name = "math.cases", span = self.span())] #[typst_macros::time(name = "math.cases", span = self.span())]
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
let delim = self.delim(ctx.styles()); let delim = self.delim(styles);
let frame = layout_vec_body( let frame = layout_vec_body(
ctx, ctx,
styles,
self.children(), self.children(),
FixedAlignment::Start, 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())) (None, Some(delim.close()))
} else { } else {
(Some(delim.open()), None) (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. /// Layout the inner contents of a vector.
fn layout_vec_body( fn layout_vec_body(
ctx: &mut MathContext, ctx: &mut MathContext,
styles: StyleChain,
column: &[Content], column: &[Content],
align: FixedAlignment, align: FixedAlignment,
row_gap: Rel<Abs>, row_gap: Rel<Abs>,
) -> SourceResult<Frame> { ) -> SourceResult<Frame> {
let gap = row_gap.relative_to(ctx.regions.base().y); 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![]; let mut flat = vec![];
for child in column { 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. /// Layout the inner contents of a matrix.
fn layout_mat_body( fn layout_mat_body(
ctx: &mut MathContext, ctx: &mut MathContext,
styles: StyleChain,
rows: &[Vec<Content>], rows: &[Vec<Content>],
augment: Option<Augment<Abs>>, augment: Option<Augment<Abs>>,
gap: Axes<Rel<Abs>>, gap: Axes<Rel<Abs>>,
@ -406,10 +415,11 @@ fn layout_mat_body(
// with font size to ensure that augmentation lines // with font size to ensure that augmentation lines
// look correct by default at all matrix sizes. // look correct by default at all matrix sizes.
// The line cap is also set to square because it looks more "correct". // 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 { let default_stroke = FixedStroke {
thickness: default_stroke_thickness, thickness: default_stroke_thickness,
paint: TextElem::fill_in(ctx.styles()).as_decoration(), paint: TextElem::fill_in(styles).as_decoration(),
cap: LineCap::Square, cap: LineCap::Square,
..Default::default() ..Default::default()
}; };
@ -443,10 +453,10 @@ fn layout_mat_body(
// individual cells are then added to it. // individual cells are then added to it.
let mut cols = vec![vec![]; ncols]; 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 (row, (ascent, descent)) in rows.iter().zip(&mut heights) {
for (cell, col) in row.iter().zip(&mut cols) { 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()); ascent.set_max(cell.ascent());
descent.set_max(cell.descent()); descent.set_max(cell.descent());
@ -454,7 +464,6 @@ fn layout_mat_body(
col.push(cell); col.push(cell);
} }
} }
ctx.unstyle();
// For each row, combine maximum ascent and descent into a row height. // 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. // 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(); let mut y = Abs::zero();
for (cell, &(ascent, descent)) in col.into_iter().zip(&heights) { 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( let pos = Point::new(
if points.is_empty() { x + (rcol - cell.width()) / 2.0 } else { x }, if points.is_empty() { x + (rcol - cell.width()) / 2.0 } else { x },
y + ascent - cell.ascent(), 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. /// Layout the outer wrapper around the body of a vector or matrix.
fn layout_delimiters( fn layout_delimiters(
ctx: &mut MathContext, ctx: &mut MathContext,
styles: StyleChain,
mut frame: Frame, mut frame: Frame,
left: Option<char>, left: Option<char>,
right: Option<char>, right: Option<char>,
span: Span, span: Span,
) -> SourceResult<()> { ) -> SourceResult<()> {
let axis = scaled!(ctx, axis_height); let font_size = scaled_font_size(ctx, styles);
let short_fall = DELIM_SHORT_FALL.scaled(ctx); let short_fall = DELIM_SHORT_FALL.at(font_size);
let axis = ctx.constants.axis_height().scaled(ctx, font_size);
let height = frame.height(); let height = frame.height();
let target = height + VERTICAL_PADDING.of(height); let target = height + VERTICAL_PADDING.of(height);
frame.set_baseline(height / 2.0 + axis); frame.set_baseline(height / 2.0 + axis);
if let Some(left) = left { if let Some(left) = left {
let mut left = let mut left = GlyphFragment::new(ctx, styles, left, span)
GlyphFragment::new(ctx, left, span).stretch_vertical(ctx, target, short_fall); .stretch_vertical(ctx, target, short_fall);
left.center_on_axis(ctx); left.center_on_axis(ctx);
ctx.push(left); ctx.push(left);
} }
ctx.push(FrameFragment::new(ctx, frame)); ctx.push(FrameFragment::new(ctx, styles, frame));
if let Some(right) = right { 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); .stretch_vertical(ctx, target, short_fall);
right.center_on_axis(ctx); right.center_on_axis(ctx);
ctx.push(right); ctx.push(right);

View File

@ -6,7 +6,8 @@ mod accent;
mod align; mod align;
mod attach; mod attach;
mod cancel; mod cancel;
mod class; #[path = "class.rs"]
mod class_;
mod equation; mod equation;
mod frac; mod frac;
mod fragment; mod fragment;
@ -24,7 +25,7 @@ pub use self::accent::*;
pub use self::align::*; pub use self::align::*;
pub use self::attach::*; pub use self::attach::*;
pub use self::cancel::*; pub use self::cancel::*;
pub use self::class::*; pub use self::class_::*;
pub use self::equation::*; pub use self::equation::*;
pub use self::frac::*; pub use self::frac::*;
pub use self::lr::*; pub use self::lr::*;
@ -46,7 +47,7 @@ use crate::foundations::{
category, Category, Content, Module, Resolve, Scope, StyleChain, category, Category, Content, Module, Resolve, Scope, StyleChain,
}; };
use crate::layout::{BoxElem, HElem, Spacing}; use crate::layout::{BoxElem, HElem, Spacing};
use crate::realize::BehavedBuilder; use crate::realize::{realize, BehavedBuilder};
use crate::text::{LinebreakElem, SpaceElem, TextElem}; use crate::text::{LinebreakElem, SpaceElem, TextElem};
/// Typst has special [syntax]($syntax/#math) and library functions to typeset /// Typst has special [syntax]($syntax/#math) and library functions to typeset
@ -215,12 +216,12 @@ pub fn module() -> Module {
/// Layout for math elements. /// Layout for math elements.
pub trait LayoutMath { pub trait LayoutMath {
/// Layout the element, producing fragment in the context. /// 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 { impl LayoutMath for Content {
#[typst_macros::time(name = "math", span = self.span())] #[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 // Directly layout the body of nested equations instead of handling it
// like a normal equation so that things like this work: // like a normal equation so that things like this work:
// ``` // ```
@ -228,11 +229,11 @@ impl LayoutMath for Content {
// $ my r^2 $ // $ my r^2 $
// ``` // ```
if let Some(elem) = self.to_packed::<EquationElem>() { 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)? { if let Some(realized) = realize(ctx.engine, self, styles)? {
return realized.layout_math(ctx); return realized.layout_math(ctx, styles);
} }
if self.is_sequence() { if self.is_sequence() {
@ -242,32 +243,28 @@ impl LayoutMath for Content {
}); });
for (child, _) in bb.finish().0.iter() { for (child, _) in bb.finish().0.iter() {
child.layout_math(ctx)?; child.layout_math(ctx, styles)?;
} }
return Ok(()); return Ok(());
} }
if let Some((elem, styles)) = self.to_styled() { if let Some((elem, local)) = self.to_styled() {
if TextElem::font_in(ctx.styles().chain(styles)) let outer = styles;
!= TextElem::font_in(ctx.styles()) let styles = outer.chain(local);
{
let frame = ctx.layout_content(self)?; if TextElem::font_in(styles) != TextElem::font_in(outer) {
ctx.push(FrameFragment::new(ctx, frame).with_spaced(true)); let frame = ctx.layout_content(elem, styles)?;
ctx.push(FrameFragment::new(ctx, styles, frame).with_spaced(true));
return Ok(()); return Ok(());
} }
let prev_map = std::mem::replace(&mut ctx.local, styles.clone()); elem.layout_math(ctx, styles)?;
let prev_size = ctx.size;
ctx.local.apply(prev_map.clone());
ctx.size = TextElem::size_in(ctx.styles());
elem.layout_math(ctx)?;
ctx.size = prev_size;
ctx.local = prev_map;
return Ok(()); return Ok(());
} }
if self.is::<SpaceElem>() { 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(()); return Ok(());
} }
@ -280,8 +277,8 @@ impl LayoutMath for Content {
if let Spacing::Rel(rel) = elem.amount() { if let Spacing::Rel(rel) = elem.amount() {
if rel.rel.is_zero() { if rel.rel.is_zero() {
ctx.push(SpacingFragment { ctx.push(SpacingFragment {
width: rel.abs.resolve(ctx.styles()), width: rel.abs.resolve(styles),
weak: elem.weak(ctx.styles()), weak: elem.weak(styles),
}); });
} }
} }
@ -289,27 +286,27 @@ impl LayoutMath for Content {
} }
if let Some(elem) = self.to_packed::<TextElem>() { if let Some(elem) = self.to_packed::<TextElem>() {
let fragment = ctx.layout_text(elem)?; let fragment = ctx.layout_text(elem, styles)?;
ctx.push(fragment); ctx.push(fragment);
return Ok(()); return Ok(());
} }
if let Some(boxed) = self.to_packed::<BoxElem>() { if let Some(boxed) = self.to_packed::<BoxElem>() {
let frame = ctx.layout_box(boxed)?; let frame = ctx.layout_box(boxed, styles)?;
ctx.push(FrameFragment::new(ctx, frame).with_spaced(true)); ctx.push(FrameFragment::new(ctx, styles, frame).with_spaced(true));
return Ok(()); return Ok(());
} }
if let Some(elem) = self.with::<dyn LayoutMath>() { 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() { 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); 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(()) Ok(())
} }

View File

@ -2,9 +2,9 @@ use ecow::EcoString;
use unicode_math_class::MathClass; use unicode_math_class::MathClass;
use crate::diag::SourceResult; 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::layout::HElem;
use crate::math::{FrameFragment, LayoutMath, Limits, MathContext, MathStyleElem, THIN}; use crate::math::{upright, FrameFragment, LayoutMath, Limits, MathContext, THIN};
use crate::text::TextElem; use crate::text::TextElem;
/// A text operator in an equation. /// A text operator in an equation.
@ -35,19 +35,19 @@ pub struct OpElem {
impl LayoutMath for Packed<OpElem> { impl LayoutMath for Packed<OpElem> {
#[typst_macros::time(name = "math.op", span = self.span())] #[typst_macros::time(name = "math.op", span = self.span())]
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
let fragment = ctx.layout_fragment(self.text())?; let fragment = ctx.layout_fragment(self.text(), styles)?;
let italics = fragment.italics_correction(); let italics = fragment.italics_correction();
let accent_attach = fragment.accent_attach(); let accent_attach = fragment.accent_attach();
let text_like = fragment.is_text_like(); let text_like = fragment.is_text_like();
ctx.push( ctx.push(
FrameFragment::new(ctx, fragment.into_frame()) FrameFragment::new(ctx, styles, fragment.into_frame())
.with_class(MathClass::Large) .with_class(MathClass::Large)
.with_italics_correction(italics) .with_italics_correction(italics)
.with_accent_attach(accent_attach) .with_accent_attach(accent_attach)
.with_text_like(text_like) .with_text_like(text_like)
.with_limits(if self.limits(ctx.styles()) { .with_limits(if self.limits(styles) {
Limits::Display Limits::Display
} else { } else {
Limits::Never Limits::Never
@ -72,7 +72,7 @@ macro_rules! ops {
let dif = |d| { let dif = |d| {
HElem::new(THIN.into()).with_weak(true).pack() 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'));
math.define("Dif", dif('D')); math.define("Dif", dif('D'));

View File

@ -1,8 +1,11 @@
use comemo::Prehashed;
use crate::diag::SourceResult; 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::layout::{Abs, Frame, FrameItem, Point, Size};
use crate::math::{ use crate::math::{
FrameFragment, GlyphFragment, LayoutMath, MathContext, MathSize, Scaled, style_cramped, EquationElem, FrameFragment, GlyphFragment, LayoutMath, MathContext,
MathSize, Scaled,
}; };
use crate::syntax::Span; use crate::syntax::Span;
use crate::text::TextElem; use crate::text::TextElem;
@ -41,8 +44,8 @@ pub struct RootElem {
impl LayoutMath for Packed<RootElem> { impl LayoutMath for Packed<RootElem> {
#[typst_macros::time(name = "math.root", span = self.span())] #[typst_macros::time(name = "math.root", span = self.span())]
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
layout(ctx, self.index(ctx.styles()).as_ref(), self.radicand(), self.span()) 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 /// See also: https://www.w3.org/TR/mathml-core/#radicals-msqrt-mroot
fn layout( fn layout(
ctx: &mut MathContext, ctx: &mut MathContext,
styles: StyleChain,
index: Option<&Content>, index: Option<&Content>,
radicand: &Content, radicand: &Content,
span: Span, span: Span,
) -> SourceResult<()> { ) -> SourceResult<()> {
let gap = scaled!( let gap = scaled!(
ctx, ctx, styles,
text: radical_vertical_gap, text: radical_vertical_gap,
display: radical_display_style_vertical_gap, display: radical_display_style_vertical_gap,
); );
let thickness = scaled!(ctx, radical_rule_thickness); let thickness = scaled!(ctx, styles, radical_rule_thickness);
let extra_ascender = scaled!(ctx, radical_extra_ascender); let extra_ascender = scaled!(ctx, styles, radical_extra_ascender);
let kern_before = scaled!(ctx, radical_kern_before_degree); let kern_before = scaled!(ctx, styles, radical_kern_before_degree);
let kern_after = scaled!(ctx, radical_kern_after_degree); let kern_after = scaled!(ctx, styles, radical_kern_after_degree);
let raise_factor = percent!(ctx, radical_degree_bottom_raise_percent); let raise_factor = percent!(ctx, radical_degree_bottom_raise_percent);
// Layout radicand. // Layout radicand.
ctx.style(ctx.style.with_cramped(true)); let cramped = style_cramped();
let radicand = ctx.layout_frame(radicand)?; let radicand = ctx.layout_frame(radicand, styles.chain(&cramped))?;
ctx.unstyle();
// Layout root symbol. // Layout root symbol.
let target = radicand.height() + thickness + gap; 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()) .stretch_vertical(ctx, target, Abs::zero())
.frame; .frame;
// Layout the index. // Layout the index.
ctx.style(ctx.style.with_size(MathSize::ScriptScript)); let sscript = Prehashed::new(EquationElem::set_size(MathSize::ScriptScript));
let index = index.map(|elem| ctx.layout_frame(elem)).transpose()?; let index = index
ctx.unstyle(); .map(|elem| ctx.layout_frame(elem, styles.chain(&sscript)))
.transpose()?;
// TeXbook, page 443, item 11 // TeXbook, page 443, item 11
// Keep original gap, and then distribute any remaining free space // Keep original gap, and then distribute any remaining free space
@ -133,7 +137,7 @@ fn layout(
FrameItem::Shape( FrameItem::Shape(
Geometry::Line(Point::with_x(radicand.width())).stroked( Geometry::Line(Point::with_x(radicand.width())).stroked(
FixedStroke::from_pair( FixedStroke::from_pair(
TextElem::fill_in(ctx.styles()).as_decoration(), TextElem::fill_in(styles).as_decoration(),
thickness, thickness,
), ),
), ),
@ -142,7 +146,7 @@ fn layout(
); );
frame.push_frame(radicand_pos, radicand); frame.push_frame(radicand_pos, radicand);
ctx.push(FrameFragment::new(ctx, frame)); ctx.push(FrameFragment::new(ctx, styles, frame));
Ok(()) Ok(())
} }

View File

@ -2,11 +2,11 @@ use std::iter::once;
use unicode_math_class::MathClass; 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::layout::{Abs, AlignElem, Em, FixedAlignment, Frame, FrameKind, Point, Size};
use crate::math::{ use crate::math::{
alignments, spacing, AlignmentResult, FrameFragment, MathContext, MathFragment, alignments, scaled_font_size, spacing, AlignmentResult, EquationElem, FrameFragment,
MathParItem, MathSize, Scaled, MathContext, MathFragment, MathParItem, MathSize,
}; };
use crate::model::ParElem; use crate::model::ParElem;
@ -61,9 +61,9 @@ impl MathRow {
// Convert variable operators into binary operators if something // Convert variable operators into binary operators if something
// precedes them and they are not preceded by a operator or comparator. // precedes them and they are not preceded by a operator or comparator.
if fragment.class() == Some(MathClass::Vary) if fragment.class() == MathClass::Vary
&& matches!( && matches!(
last.and_then(|i| resolved[i].class()), last.map(|i| resolved[i].class()),
Some( Some(
MathClass::Normal MathClass::Normal
| MathClass::Alphabetic | MathClass::Alphabetic
@ -131,8 +131,8 @@ impl MathRow {
if self.0.len() == 1 { if self.0.len() == 1 {
self.0 self.0
.first() .first()
.and_then(|fragment| fragment.class()) .map(|fragment| fragment.class())
.unwrap_or(MathClass::Special) .unwrap_or(MathClass::Normal)
} else { } else {
// FrameFragment::new() (inside 'into_fragment' in this branch) defaults // FrameFragment::new() (inside 'into_fragment' in this branch) defaults
// to MathClass::Normal for its class. // to MathClass::Normal for its class.
@ -140,23 +140,23 @@ impl MathRow {
} }
} }
pub fn into_frame(self, ctx: &MathContext) -> Frame { pub fn into_frame(self, ctx: &MathContext, styles: StyleChain) -> Frame {
let styles = ctx.styles();
let align = AlignElem::alignment_in(styles).resolve(styles).x; 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 { if self.0.len() == 1 {
self.0.into_iter().next().unwrap() self.0.into_iter().next().unwrap()
} else { } else {
FrameFragment::new(ctx, self.into_frame(ctx)).into() FrameFragment::new(ctx, styles, self.into_frame(ctx, styles)).into()
} }
} }
pub fn into_aligned_frame( pub fn into_aligned_frame(
self, self,
ctx: &MathContext, ctx: &MathContext,
styles: StyleChain,
points: &[Abs], points: &[Abs],
align: FixedAlignment, align: FixedAlignment,
) -> Frame { ) -> Frame {
@ -164,10 +164,11 @@ impl MathRow {
return self.into_line_frame(points, align); return self.into_line_frame(points, align);
} }
let leading = if ctx.style.size >= MathSize::Text { let leading = if EquationElem::size_in(styles) >= MathSize::Text {
ParElem::leading_in(ctx.styles()) ParElem::leading_in(styles)
} else { } else {
TIGHT_LEADING.scaled(ctx) let font_size = scaled_font_size(ctx, styles);
TIGHT_LEADING.at(font_size)
}; };
let mut rows: Vec<_> = self.rows(); let mut rows: Vec<_> = self.rows();
@ -272,8 +273,7 @@ impl MathRow {
let mut space_is_visible = false; let mut space_is_visible = false;
let is_relation = let is_relation = |f: &MathFragment| matches!(f.class(), MathClass::Relation);
|f: &MathFragment| matches!(f.class(), Some(MathClass::Relation));
let is_space = |f: &MathFragment| { let is_space = |f: &MathFragment| {
matches!(f, MathFragment::Space(_) | MathFragment::Spacing(_)) matches!(f, MathFragment::Space(_) | MathFragment::Spacing(_))
}; };
@ -302,8 +302,8 @@ impl MathRow {
frame.push_frame(pos, fragment.into_frame()); frame.push_frame(pos, fragment.into_frame());
empty = false; empty = false;
if class == Some(MathClass::Binary) if class == MathClass::Binary
|| (class == Some(MathClass::Relation) || (class == MathClass::Relation
&& !iter.peek().map(is_relation).unwrap_or_default()) && !iter.peek().map(is_relation).unwrap_or_default())
{ {
let mut frame_prev = std::mem::replace( let mut frame_prev = std::mem::replace(

View File

@ -27,15 +27,14 @@ pub(super) fn spacing(
) -> Option<MathFragment> { ) -> Option<MathFragment> {
use MathClass::*; use MathClass::*;
let class = |f: &MathFragment| f.class().unwrap_or(Special);
let resolve = |v: Em, size_ref: &MathFragment| -> Option<MathFragment> { let resolve = |v: Em, size_ref: &MathFragment| -> Option<MathFragment> {
let width = size_ref.font_size().map_or(Abs::zero(), |size| v.at(size)); let width = size_ref.font_size().map_or(Abs::zero(), |size| v.at(size));
Some(SpacingFragment { width, weak: false }.into()) Some(SpacingFragment { width, weak: false }.into())
}; };
let script = 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 // No spacing before punctuation; thin spacing after punctuation, unless
// in script size. // in script size.
(_, Punctuation) => None, (_, Punctuation) => None,

View File

@ -45,7 +45,7 @@ fn stretch_glyph(
.table .table
.variants .variants
.and_then(|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 { if horizontal {
variants.horizontal_constructions variants.horizontal_constructions
} else { } else {
@ -106,12 +106,12 @@ fn assemble(
let mut growable = Abs::zero(); let mut growable = Abs::zero();
while let Some(part) = parts.next() { 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() { if let Some(next) = parts.peek() {
let max_overlap = part let max_overlap = part
.end_connector_length .end_connector_length
.min(next.start_connector_length) .min(next.start_connector_length)
.scaled(ctx); .scaled(ctx, base.font_size);
advance -= max_overlap; advance -= max_overlap;
growable += max_overlap - min_overlap; growable += max_overlap - min_overlap;
@ -136,10 +136,12 @@ fn assemble(
let mut selected = vec![]; let mut selected = vec![];
let mut parts = parts(assembly, repeat).peekable(); let mut parts = parts(assembly, repeat).peekable();
while let Some(part) = parts.next() { 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() { if let Some(next) = parts.peek() {
let max_overlap = let max_overlap = part
part.end_connector_length.min(next.start_connector_length).scaled(ctx); .end_connector_length
.min(next.start_connector_length)
.scaled(ctx, base.font_size);
advance -= max_overlap; advance -= max_overlap;
advance += ratio * (max_overlap - min_overlap); advance += ratio * (max_overlap - min_overlap);
} }
@ -156,7 +158,7 @@ fn assemble(
size = Size::new(full, height); size = Size::new(full, height);
baseline = base.ascent; baseline = base.ascent;
} else { } 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(); let width = selected.iter().map(|(f, _)| f.width).max().unwrap_or_default();
size = Size::new(width, full); size = Size::new(width, full);
baseline = full / 2.0 + axis; baseline = full / 2.0 + axis;
@ -183,11 +185,11 @@ fn assemble(
c: base.c, c: base.c,
id: None, id: None,
frame, frame,
style: base.style,
font_size: base.font_size, font_size: base.font_size,
italics_correction: Abs::zero(), italics_correction: Abs::zero(),
accent_attach, accent_attach,
class: base.class, class: base.class,
math_size: base.math_size,
span: base.span, span: base.span,
limits: base.limits, limits: base.limits,
mid_stretched: None, mid_stretched: None,

View File

@ -1,11 +1,9 @@
use unicode_math_class::MathClass; use comemo::Prehashed;
use crate::diag::SourceResult; use crate::foundations::{func, Cast, Content, Smart, Style, StyleChain};
use crate::foundations::{ use crate::layout::Abs;
elem, func, Cast, Content, NativeElement, Packed, Smart, StyleChain, use crate::math::{EquationElem, MathContext};
}; use crate::text::TextElem;
use crate::math::{LayoutMath, MathContext};
use crate::syntax::Span;
/// Bold font style in math. /// Bold font style in math.
/// ///
@ -14,12 +12,10 @@ use crate::syntax::Span;
/// ``` /// ```
#[func] #[func]
pub fn bold( pub fn bold(
/// The call span of this function.
span: Span,
/// The content to style. /// The content to style.
body: Content, body: Content,
) -> 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. /// Upright (non-italic) font style in math.
@ -29,12 +25,10 @@ pub fn bold(
/// ``` /// ```
#[func] #[func]
pub fn upright( pub fn upright(
/// The call span of this function.
span: Span,
/// The content to style. /// The content to style.
body: Content, body: Content,
) -> 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. /// Italic font style in math.
@ -42,27 +36,21 @@ pub fn upright(
/// For roman letters and greek lowercase letters, this is already the default. /// For roman letters and greek lowercase letters, this is already the default.
#[func] #[func]
pub fn italic( pub fn italic(
/// The call span of this function.
span: Span,
/// The content to style. /// The content to style.
body: Content, body: Content,
) -> 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. /// Serif (roman) font style in math.
/// ///
/// This is already the default. /// This is already the default.
#[func] #[func]
pub fn serif( pub fn serif(
/// The call span of this function.
span: Span,
/// The content to style. /// The content to style.
body: Content, body: Content,
) -> Content { ) -> Content {
MathStyleElem::new(body) body.styled(EquationElem::set_variant(MathVariant::Serif))
.with_variant(Some(MathVariant::Serif))
.pack()
.spanned(span)
} }
/// Sans-serif font style in math. /// Sans-serif font style in math.
@ -72,15 +60,10 @@ pub fn serif(
/// ``` /// ```
#[func(title = "Sans Serif")] #[func(title = "Sans Serif")]
pub fn sans( pub fn sans(
/// The call span of this function.
span: Span,
/// The content to style. /// The content to style.
body: Content, body: Content,
) -> Content { ) -> Content {
MathStyleElem::new(body) body.styled(EquationElem::set_variant(MathVariant::Sans))
.with_variant(Some(MathVariant::Sans))
.pack()
.spanned(span)
} }
/// Calligraphic font style in math. /// Calligraphic font style in math.
@ -90,15 +73,10 @@ pub fn sans(
/// ``` /// ```
#[func(title = "Calligraphic")] #[func(title = "Calligraphic")]
pub fn cal( pub fn cal(
/// The call span of this function.
span: Span,
/// The content to style. /// The content to style.
body: Content, body: Content,
) -> Content { ) -> Content {
MathStyleElem::new(body) body.styled(EquationElem::set_variant(MathVariant::Cal))
.with_variant(Some(MathVariant::Cal))
.pack()
.spanned(span)
} }
/// Fraktur font style in math. /// Fraktur font style in math.
@ -108,15 +86,10 @@ pub fn cal(
/// ``` /// ```
#[func(title = "Fraktur")] #[func(title = "Fraktur")]
pub fn frak( pub fn frak(
/// The call span of this function.
span: Span,
/// The content to style. /// The content to style.
body: Content, body: Content,
) -> Content { ) -> Content {
MathStyleElem::new(body) body.styled(EquationElem::set_variant(MathVariant::Frak))
.with_variant(Some(MathVariant::Frak))
.pack()
.spanned(span)
} }
/// Monospace font style in math. /// Monospace font style in math.
@ -126,15 +99,10 @@ pub fn frak(
/// ``` /// ```
#[func(title = "Monospace")] #[func(title = "Monospace")]
pub fn mono( pub fn mono(
/// The call span of this function.
span: Span,
/// The content to style. /// The content to style.
body: Content, body: Content,
) -> Content { ) -> Content {
MathStyleElem::new(body) body.styled(EquationElem::set_variant(MathVariant::Mono))
.with_variant(Some(MathVariant::Mono))
.pack()
.spanned(span)
} }
/// Blackboard bold (double-struck) font style in math. /// Blackboard bold (double-struck) font style in math.
@ -149,15 +117,10 @@ pub fn mono(
/// ``` /// ```
#[func(title = "Blackboard Bold")] #[func(title = "Blackboard Bold")]
pub fn bb( pub fn bb(
/// The call span of this function.
span: Span,
/// The content to style. /// The content to style.
body: Content, body: Content,
) -> Content { ) -> Content {
MathStyleElem::new(body) body.styled(EquationElem::set_variant(MathVariant::Bb))
.with_variant(Some(MathVariant::Bb))
.pack()
.spanned(span)
} }
/// Forced display style in math. /// Forced display style in math.
@ -169,8 +132,6 @@ pub fn bb(
/// ``` /// ```
#[func(title = "Display Size")] #[func(title = "Display Size")]
pub fn display( pub fn display(
/// The call span of this function.
span: Span,
/// The content to size. /// The content to size.
body: Content, body: Content,
/// Whether to impose a height restriction for exponents, like regular sub- /// Whether to impose a height restriction for exponents, like regular sub-
@ -179,11 +140,8 @@ pub fn display(
#[default(false)] #[default(false)]
cramped: bool, cramped: bool,
) -> Content { ) -> Content {
MathStyleElem::new(body) body.styled(EquationElem::set_size(MathSize::Display))
.with_size(Some(MathSize::Display)) .styled(EquationElem::set_cramped(cramped))
.with_cramped(Some(cramped))
.pack()
.spanned(span)
} }
/// Forced inline (text) style in math. /// Forced inline (text) style in math.
@ -196,8 +154,6 @@ pub fn display(
/// ``` /// ```
#[func(title = "Inline Size")] #[func(title = "Inline Size")]
pub fn inline( pub fn inline(
/// The call span of this function.
span: Span,
/// The content to size. /// The content to size.
body: Content, body: Content,
/// Whether to impose a height restriction for exponents, like regular sub- /// Whether to impose a height restriction for exponents, like regular sub-
@ -206,11 +162,8 @@ pub fn inline(
#[default(false)] #[default(false)]
cramped: bool, cramped: bool,
) -> Content { ) -> Content {
MathStyleElem::new(body) body.styled(EquationElem::set_size(MathSize::Text))
.with_size(Some(MathSize::Text)) .styled(EquationElem::set_cramped(cramped))
.with_cramped(Some(cramped))
.pack()
.spanned(span)
} }
/// Forced script style in math. /// Forced script style in math.
@ -222,8 +175,6 @@ pub fn inline(
/// ``` /// ```
#[func(title = "Script Size")] #[func(title = "Script Size")]
pub fn script( pub fn script(
/// The call span of this function.
span: Span,
/// The content to size. /// The content to size.
body: Content, body: Content,
/// Whether to impose a height restriction for exponents, like regular sub- /// Whether to impose a height restriction for exponents, like regular sub-
@ -232,11 +183,8 @@ pub fn script(
#[default(true)] #[default(true)]
cramped: bool, cramped: bool,
) -> Content { ) -> Content {
MathStyleElem::new(body) body.styled(EquationElem::set_size(MathSize::Script))
.with_size(Some(MathSize::Script)) .styled(EquationElem::set_cramped(cramped))
.with_cramped(Some(cramped))
.pack()
.spanned(span)
} }
/// Forced second script style in math. /// Forced second script style in math.
@ -249,8 +197,6 @@ pub fn script(
/// ``` /// ```
#[func(title = "Script-Script Size")] #[func(title = "Script-Script Size")]
pub fn sscript( pub fn sscript(
/// The call span of this function.
span: Span,
/// The content to size. /// The content to size.
body: Content, body: Content,
/// Whether to impose a height restriction for exponents, like regular sub- /// Whether to impose a height restriction for exponents, like regular sub-
@ -259,141 +205,8 @@ pub fn sscript(
#[default(true)] #[default(true)]
cramped: bool, cramped: bool,
) -> Content { ) -> Content {
MathStyleElem::new(body) body.styled(EquationElem::set_size(MathSize::ScriptScript))
.with_size(Some(MathSize::ScriptScript)) .styled(EquationElem::set_cramped(cramped))
.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)
}
} }
/// The size of elements in an equation. /// The size of elements in an equation.
@ -412,7 +225,8 @@ pub enum MathSize {
} }
impl MathSize { impl MathSize {
pub(super) fn factor(self, ctx: &MathContext) -> f64 { /// The scaling factor.
pub fn factor(self, ctx: &MathContext) -> f64 {
match self { match self {
Self::Display | Self::Text => 1.0, Self::Display | Self::Text => 1.0,
Self::Script => percent!(ctx, script_percent_scale_down), Self::Script => percent!(ctx, script_percent_scale_down),
@ -422,8 +236,9 @@ impl MathSize {
} }
/// A mathematical style variant, as defined by Unicode. /// 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 { pub enum MathVariant {
#[default]
Serif, Serif,
Sans, Sans,
Cal, Cal,
@ -432,21 +247,53 @@ pub enum MathVariant {
Bb, Bb,
} }
impl Default for MathVariant { /// Get the font size scaled with the `MathSize`.
fn default() -> Self { pub fn scaled_font_size(ctx: &MathContext, styles: StyleChain) -> Abs {
Self::Serif 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. /// Select the correct styled math letter.
/// ///
/// https://www.w3.org/TR/mathml-core/#new-text-transform-mappings /// <https://www.w3.org/TR/mathml-core/#new-text-transform-mappings>
/// https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols /// <https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols>
pub(super) fn styled_char(style: MathStyle, c: char) -> char { pub fn styled_char(styles: StyleChain, c: char) -> char {
use MathVariant::*; use MathVariant::*;
let MathStyle { variant, bold, .. } = style; let variant = EquationElem::variant_in(styles);
let italic = style.italic.unwrap_or(matches!( let bold = EquationElem::bold_in(styles);
let italic = EquationElem::italic_in(styles).unwrap_or(matches!(
c, c,
'a'..='z' | 'ı' | 'ȷ' | 'A'..='Z' | 'α'..='ω' | 'a'..='z' | 'ı' | 'ȷ' | 'A'..='Z' | 'α'..='ω' |
'∂' | 'ϵ' | 'ϑ' | 'ϰ' | 'ϕ' | 'ϱ' | 'ϖ' '∂' | 'ϵ' | 'ϑ' | 'ϰ' | 'ϕ' | 'ϱ' | 'ϖ'

View File

@ -1,11 +1,9 @@
use unicode_math_class::MathClass;
use crate::diag::SourceResult; 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::layout::{Abs, Em, FixedAlignment, Frame, FrameItem, Point, Size};
use crate::math::{ use crate::math::{
alignments, AlignmentResult, FrameFragment, GlyphFragment, LayoutMath, MathContext, alignments, scaled_font_size, style_cramped, style_for_subscript, AlignmentResult,
MathRow, Scaled, FrameFragment, GlyphFragment, LayoutMath, MathContext, MathRow, Scaled,
}; };
use crate::syntax::Span; use crate::syntax::Span;
use crate::text::TextElem; use crate::text::TextElem;
@ -34,8 +32,8 @@ pub struct UnderlineElem {
impl LayoutMath for Packed<UnderlineElem> { impl LayoutMath for Packed<UnderlineElem> {
#[typst_macros::time(name = "math.underline", span = self.span())] #[typst_macros::time(name = "math.underline", span = self.span())]
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
layout_underoverline(ctx, self.body(), self.span(), LineKind::Under) layout_underoverline(ctx, styles, self.body(), self.span(), LineKind::Under)
} }
} }
@ -53,14 +51,15 @@ pub struct OverlineElem {
impl LayoutMath for Packed<OverlineElem> { impl LayoutMath for Packed<OverlineElem> {
#[typst_macros::time(name = "math.overline", span = self.span())] #[typst_macros::time(name = "math.overline", span = self.span())]
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
layout_underoverline(ctx, self.body(), self.span(), LineKind::Over) layout_underoverline(ctx, styles, self.body(), self.span(), LineKind::Over)
} }
} }
/// layout under- or overlined content /// layout under- or overlined content
fn layout_underoverline( fn layout_underoverline(
ctx: &mut MathContext, ctx: &mut MathContext,
styles: StyleChain,
body: &Content, body: &Content,
span: Span, span: Span,
line: LineKind, line: LineKind,
@ -68,26 +67,25 @@ fn layout_underoverline(
let (extra_height, content, line_pos, content_pos, baseline, bar_height); let (extra_height, content, line_pos, content_pos, baseline, bar_height);
match line { match line {
LineKind::Under => { LineKind::Under => {
let sep = scaled!(ctx, underbar_extra_descender); let sep = scaled!(ctx, styles, underbar_extra_descender);
bar_height = scaled!(ctx, underbar_rule_thickness); bar_height = scaled!(ctx, styles, underbar_rule_thickness);
let gap = scaled!(ctx, underbar_vertical_gap); let gap = scaled!(ctx, styles, underbar_vertical_gap);
extra_height = sep + bar_height + 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); line_pos = Point::with_y(content.height() + gap + bar_height / 2.0);
content_pos = Point::zero(); content_pos = Point::zero();
baseline = content.ascent() baseline = content.ascent()
} }
LineKind::Over => { LineKind::Over => {
let sep = scaled!(ctx, overbar_extra_ascender); let sep = scaled!(ctx, styles, overbar_extra_ascender);
bar_height = scaled!(ctx, overbar_rule_thickness); bar_height = scaled!(ctx, styles, overbar_rule_thickness);
let gap = scaled!(ctx, overbar_vertical_gap); let gap = scaled!(ctx, styles, overbar_vertical_gap);
extra_height = sep + bar_height + gap; extra_height = sep + bar_height + gap;
ctx.style(ctx.style.with_cramped(true)); let cramped = style_cramped();
content = ctx.layout_fragment(body)?; content = ctx.layout_fragment(body, styles.chain(&cramped))?;
ctx.unstyle();
line_pos = Point::with_y(sep + bar_height / 2.0); line_pos = Point::with_y(sep + bar_height / 2.0);
content_pos = Point::with_y(extra_height); content_pos = Point::with_y(extra_height);
@ -99,7 +97,7 @@ fn layout_underoverline(
let height = content.height() + extra_height; let height = content.height() + extra_height;
let size = Size::new(width, 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); let mut frame = Frame::soft(size);
frame.set_baseline(baseline); frame.set_baseline(baseline);
frame.push_frame(content_pos, content.into_frame()); frame.push_frame(content_pos, content.into_frame());
@ -107,7 +105,7 @@ fn layout_underoverline(
line_pos, line_pos,
FrameItem::Shape( FrameItem::Shape(
Geometry::Line(Point::with_x(width)).stroked(FixedStroke { 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, thickness: bar_height,
..FixedStroke::default() ..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(()) Ok(())
} }
@ -138,11 +136,12 @@ pub struct UnderbraceElem {
impl LayoutMath for Packed<UnderbraceElem> { impl LayoutMath for Packed<UnderbraceElem> {
#[typst_macros::time(name = "math.underbrace", span = self.span())] #[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( layout_underoverspreader(
ctx, ctx,
styles,
self.body(), self.body(),
&self.annotation(ctx.styles()), &self.annotation(styles),
'⏟', '⏟',
BRACE_GAP, BRACE_GAP,
false, false,
@ -169,11 +168,12 @@ pub struct OverbraceElem {
impl LayoutMath for Packed<OverbraceElem> { impl LayoutMath for Packed<OverbraceElem> {
#[typst_macros::time(name = "math.overbrace", span = self.span())] #[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( layout_underoverspreader(
ctx, ctx,
styles,
self.body(), self.body(),
&self.annotation(ctx.styles()), &self.annotation(styles),
'⏞', '⏞',
BRACE_GAP, BRACE_GAP,
true, true,
@ -200,11 +200,12 @@ pub struct UnderbracketElem {
impl LayoutMath for Packed<UnderbracketElem> { impl LayoutMath for Packed<UnderbracketElem> {
#[typst_macros::time(name = "math.underbrace", span = self.span())] #[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( layout_underoverspreader(
ctx, ctx,
styles,
self.body(), self.body(),
&self.annotation(ctx.styles()), &self.annotation(styles),
'⎵', '⎵',
BRACKET_GAP, BRACKET_GAP,
false, false,
@ -231,11 +232,12 @@ pub struct OverbracketElem {
impl LayoutMath for Packed<OverbracketElem> { impl LayoutMath for Packed<OverbracketElem> {
#[typst_macros::time(name = "math.overbracket", span = self.span())] #[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( layout_underoverspreader(
ctx, ctx,
styles,
self.body(), self.body(),
&self.annotation(ctx.styles()), &self.annotation(styles),
'⎴', '⎴',
BRACKET_GAP, BRACKET_GAP,
true, true,
@ -245,8 +247,10 @@ impl LayoutMath for Packed<OverbracketElem> {
} }
/// Layout an over- or underbrace-like object. /// Layout an over- or underbrace-like object.
#[allow(clippy::too_many_arguments)]
fn layout_underoverspreader( fn layout_underoverspreader(
ctx: &mut MathContext, ctx: &mut MathContext,
styles: StyleChain,
body: &Content, body: &Content,
annotation: &Option<Content>, annotation: &Option<Content>,
c: char, c: char,
@ -254,26 +258,31 @@ fn layout_underoverspreader(
reverse: bool, reverse: bool,
span: Span, span: Span,
) -> SourceResult<()> { ) -> SourceResult<()> {
let gap = gap.scaled(ctx); let font_size = scaled_font_size(ctx, styles);
let body = ctx.layout_row(body)?; let gap = gap.at(font_size);
let body = ctx.layout_row(body, styles)?;
let body_class = body.class(); let body_class = body.class();
let body = body.into_fragment(ctx); let body = body.into_fragment(ctx, styles);
let glyph = GlyphFragment::new(ctx, c, span); let glyph = GlyphFragment::new(ctx, styles, c, span);
let stretched = glyph.stretch_horizontal(ctx, body.width(), Abs::zero()); let stretched = glyph.stretch_horizontal(ctx, body.width(), Abs::zero());
let mut rows = vec![MathRow::new(vec![body]), stretched.into()]; 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 { } else {
ctx.style.for_superscript() sub_style = style_for_subscript(styles);
}); styles.chain(&sub_style)
};
rows.extend( rows.extend(
annotation annotation
.as_ref() .as_ref()
.map(|annotation| ctx.layout_row(annotation)) .map(|annotation| ctx.layout_row(annotation, row_styles))
.transpose()?, .transpose()?,
); );
ctx.unstyle();
let mut baseline = 0; let mut baseline = 0;
if reverse { if reverse {
@ -281,8 +290,8 @@ fn layout_underoverspreader(
baseline = rows.len() - 1; baseline = rows.len() - 1;
} }
let frame = stack(ctx, rows, FixedAlignment::Center, gap, baseline); let frame = stack(ctx, styles, rows, FixedAlignment::Center, gap, baseline);
ctx.push(FrameFragment::new(ctx, frame).with_class(body_class)); ctx.push(FrameFragment::new(ctx, styles, frame).with_class(body_class));
Ok(()) Ok(())
} }
@ -293,6 +302,7 @@ fn layout_underoverspreader(
/// row for the whole frame. /// row for the whole frame.
pub(super) fn stack( pub(super) fn stack(
ctx: &MathContext, ctx: &MathContext,
styles: StyleChain,
rows: Vec<MathRow>, rows: Vec<MathRow>,
align: FixedAlignment, align: FixedAlignment,
gap: Abs, gap: Abs,
@ -302,7 +312,7 @@ pub(super) fn stack(
let AlignmentResult { points, width } = alignments(&rows); let AlignmentResult { points, width } = alignments(&rows);
let rows: Vec<_> = rows let rows: Vec<_> = rows
.into_iter() .into_iter()
.map(|row| row.into_aligned_frame(ctx, &points, align)) .map(|row| row.into_aligned_frame(ctx, styles, &points, align))
.collect(); .collect();
let mut y = Abs::zero(); let mut y = Abs::zero();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -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$