diff options
Diffstat (limited to 'crates/typst-library/src/math/ctx.rs')
| -rw-r--r-- | crates/typst-library/src/math/ctx.rs | 268 |
1 files changed, 268 insertions, 0 deletions
diff --git a/crates/typst-library/src/math/ctx.rs b/crates/typst-library/src/math/ctx.rs new file mode 100644 index 00000000..a1dc6cf4 --- /dev/null +++ b/crates/typst-library/src/math/ctx.rs @@ -0,0 +1,268 @@ +use ttf_parser::math::MathValue; +use typst::font::{FontStyle, FontWeight}; +use typst::model::realize; +use unicode_segmentation::UnicodeSegmentation; + +use super::*; + +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, $name:ident) => { + $ctx.constants.$name().scaled($ctx) + }; +} + +macro_rules! percent { + ($ctx:expr, $name:ident) => { + $ctx.constants.$name() as f64 / 100.0 + }; +} + +/// The context for math layout. +pub struct MathContext<'a, 'b, 'v> { + pub vt: &'v mut Vt<'b>, + pub regions: Regions<'static>, + pub font: &'a Font, + pub ttf: &'a ttf_parser::Face<'a>, + pub table: ttf_parser::math::Table<'a>, + pub constants: ttf_parser::math::Constants<'a>, + pub ssty_table: Option<ttf_parser::gsub::AlternateSubstitution<'a>>, + pub space_width: Em, + 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> { + pub fn new( + vt: &'v mut Vt<'b>, + styles: StyleChain<'a>, + regions: Regions, + font: &'a Font, + block: bool, + ) -> Self { + let table = font.ttf().tables().math.unwrap(); + let constants = table.constants.unwrap(); + + let ssty_table = font + .ttf() + .tables() + .gsub + .and_then(|gsub| { + gsub.features + .find(ttf_parser::Tag::from_bytes(b"ssty")) + .and_then(|feature| feature.lookup_indices.get(0)) + .and_then(|index| gsub.lookups.get(index)) + }) + .and_then(|ssty| { + ssty.subtables.get::<ttf_parser::gsub::SubstitutionSubtable>(0) + }) + .and_then(|ssty| match ssty { + ttf_parser::gsub::SubstitutionSubtable::Alternate(alt_glyphs) => { + Some(alt_glyphs) + } + _ => None, + }); + + let size = TextElem::size_in(styles); + let ttf = font.ttf(); + let space_width = ttf + .glyph_index(' ') + .and_then(|id| ttf.glyph_hor_advance(id)) + .map(|advance| font.to_em(advance)) + .unwrap_or(THICK); + + let variant = variant(styles); + Self { + vt, + regions: Regions::one(regions.base(), Axes::splat(false)), + font, + ttf: font.ttf(), + table, + constants, + ssty_table, + space_width, + fragments: vec![], + local: Styles::new(), + style: MathStyle { + variant: MathVariant::Serif, + size: if block { MathSize::Display } else { MathSize::Text }, + 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![], + } + } + + pub fn push(&mut self, fragment: impl Into<MathFragment>) { + self.fragments.push(fragment.into()); + } + + pub fn extend(&mut self, fragments: Vec<MathFragment>) { + self.fragments.extend(fragments); + } + + pub fn layout_fragment( + &mut self, + elem: &dyn LayoutMath, + ) -> SourceResult<MathFragment> { + let row = self.layout_fragments(elem)?; + Ok(MathRow::new(row).into_fragment(self)) + } + + pub fn layout_fragments( + &mut self, + elem: &dyn LayoutMath, + ) -> SourceResult<Vec<MathFragment>> { + let prev = std::mem::take(&mut self.fragments); + elem.layout_math(self)?; + 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)?; + 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_content(&mut self, content: &Content) -> SourceResult<Frame> { + Ok(content + .layout(self.vt, self.outer.chain(&self.local), self.regions)? + .into_frame()) + } + + pub fn layout_text(&mut self, elem: &TextElem) -> SourceResult<MathFragment> { + let text = elem.text(); + let span = elem.span(); + let mut chars = text.chars(); + 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)) + { + // A single letter that is available in the math font. + match self.style.size { + MathSize::Display => { + if glyph.class == Some(MathClass::Large) { + let height = scaled!(self, display_operator_min_height); + glyph.stretch_vertical(self, height, Abs::zero()).into() + } else { + glyph.into() + } + } + MathSize::Script => { + glyph.make_scriptsize(self); + glyph.into() + } + MathSize::ScriptScript => { + glyph.make_scriptscriptsize(self); + glyph.into() + } + _ => glyph.into(), + } + } else if text.chars().all(|c| c.is_ascii_digit()) { + // 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 frame = MathRow::new(fragments).into_frame(self); + FrameFragment::new(self, frame).into() + } else { + // Anything else is handled by Typst's standard text layout. + let spaced = text.graphemes(true).nth(1).is_some(); + 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 frame = self.layout_content(&TextElem::packed(text).spanned(span))?; + FrameFragment::new(self, frame) + .with_class(MathClass::Alphabetic) + .with_spaced(spaced) + .into() + }; + Ok(fragment) + } + + pub fn styles(&self) -> StyleChain { + self.outer.chain(&self.local) + } + + pub fn realize(&mut self, content: &Content) -> SourceResult<Option<Content>> { + realize(self.vt, 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 { + FontWeight::REGULAR + })); + 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; +} + +impl Scaled for i16 { + fn scaled(self, ctx: &MathContext) -> Abs { + ctx.font.to_em(self).scaled(ctx) + } +} + +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) + } +} + +impl Scaled for MathValue<'_> { + fn scaled(self, ctx: &MathContext) -> Abs { + self.value.scaled(ctx) + } +} |
