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

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

View File

@ -445,17 +445,16 @@ impl<'a> StyleChain<'a> {
Self { head: &root.0, tail: None }
}
/// 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>>,

View File

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

View File

@ -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(())
}

View File

@ -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();

View File

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

View File

@ -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(())
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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(())

View File

@ -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))
}

View File

@ -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);

View File

@ -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);

View File

@ -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(())
}

View File

@ -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'));

View File

@ -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(())
}

View File

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

View File

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

View File

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

View File

@ -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' | 'α'..='ω' |
'∂' | 'ϵ' | 'ϑ' | 'ϰ' | 'ϕ' | 'ϱ' | 'ϖ'

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,7 @@
// Test show rules on equations.
---
This is small: $sum_(i=0)^n$
#show math.equation: math.display
This is big: $sum_(i=0)^n$