summaryrefslogtreecommitdiff
path: root/crates/typst-library/src/math/ctx.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-library/src/math/ctx.rs')
-rw-r--r--crates/typst-library/src/math/ctx.rs268
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)
+ }
+}