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