diff --git a/crates/typst-layout/src/inline/mod.rs b/crates/typst-layout/src/inline/mod.rs index 5ef820d07..6cafb9b00 100644 --- a/crates/typst-layout/src/inline/mod.rs +++ b/crates/typst-layout/src/inline/mod.rs @@ -9,6 +9,7 @@ mod prepare; mod shaping; pub use self::box_::layout_box; +pub use self::shaping::create_shape_plan; use comemo::{Track, Tracked, TrackedMut}; use typst_library::diag::SourceResult; diff --git a/crates/typst-layout/src/inline/shaping.rs b/crates/typst-layout/src/inline/shaping.rs index ca723c0a5..2a1fc8586 100644 --- a/crates/typst-layout/src/inline/shaping.rs +++ b/crates/typst-layout/src/inline/shaping.rs @@ -1,18 +1,16 @@ use std::borrow::Cow; use std::fmt::{self, Debug, Formatter}; -use std::str::FromStr; use std::sync::Arc; use az::SaturatingAs; -use ecow::EcoString; use rustybuzz::{BufferFlags, ShapePlan, UnicodeBuffer}; use ttf_parser::Tag; use typst_library::engine::Engine; use typst_library::foundations::{Smart, StyleChain}; use typst_library::layout::{Abs, Dir, Em, Frame, FrameItem, Point, Size}; use typst_library::text::{ - families, features, is_default_ignorable, variant, Font, FontFamily, FontVariant, - Glyph, Lang, Region, TextEdgeBounds, TextElem, TextItem, + families, features, is_default_ignorable, language, variant, Font, FontFamily, + FontVariant, Glyph, Lang, Region, TextEdgeBounds, TextElem, TextItem, }; use typst_library::World; use typst_utils::SliceExt; @@ -295,6 +293,8 @@ impl<'a> ShapedText<'a> { + justification_left + justification_right, x_offset: shaped.x_offset + justification_left, + y_advance: Em::zero(), + y_offset: Em::zero(), range: (shaped.range.start - range.start).saturating_as() ..(shaped.range.end - range.start).saturating_as(), span, @@ -934,7 +934,7 @@ fn shape_segment<'a>( /// Create a shape plan. #[comemo::memoize] -fn create_shape_plan( +pub fn create_shape_plan( font: &Font, direction: rustybuzz::Direction, script: rustybuzz::Script, @@ -1044,20 +1044,8 @@ fn calculate_adjustability(ctx: &mut ShapingContext, lang: Lang, region: Option< /// Difference between non-breaking and normal space. fn nbsp_delta(font: &Font) -> Option { - let space = font.ttf().glyph_index(' ')?.0; let nbsp = font.ttf().glyph_index('\u{00A0}')?.0; - Some(font.advance(nbsp)? - font.advance(space)?) -} - -/// Process the language and region of a style chain into a -/// rustybuzz-compatible BCP 47 language. -fn language(styles: StyleChain) -> rustybuzz::Language { - let mut bcp: EcoString = TextElem::lang_in(styles).as_str().into(); - if let Some(region) = TextElem::region_in(styles) { - bcp.push('-'); - bcp.push_str(region.as_str()); - } - rustybuzz::Language::from_str(&bcp).unwrap() + Some(font.advance(nbsp)? - font.space_width()?) } /// Returns true if all glyphs in `glyphs` have ranges within the range `range`. diff --git a/crates/typst-layout/src/math/accent.rs b/crates/typst-layout/src/math/accent.rs index 73d821019..3ac8a7a1a 100644 --- a/crates/typst-layout/src/math/accent.rs +++ b/crates/typst-layout/src/math/accent.rs @@ -3,7 +3,9 @@ use typst_library::foundations::{Packed, StyleChain}; use typst_library::layout::{Em, Frame, Point, Size}; use typst_library::math::{Accent, AccentElem}; -use super::{style_cramped, FrameFragment, GlyphFragment, MathContext, MathFragment}; +use super::{ + style_cramped, style_dtls, style_flac, FrameFragment, MathContext, MathFragment, +}; /// How much the accent can be shorter than the base. const ACCENT_SHORT_FALL: Em = Em::new(0.5); @@ -15,15 +17,12 @@ pub fn layout_accent( ctx: &mut MathContext, styles: StyleChain, ) -> SourceResult<()> { - let cramped = style_cramped(); - let mut base = ctx.layout_into_fragment(&elem.base, styles.chain(&cramped))?; + // Try to replace the base glyph with its dotless variant. + let dtls = style_dtls(); + let base_styles = if elem.dotless(styles) { styles.chain(&dtls) } else { styles }; - // Try to replace a glyph with its dotless variant. - if elem.dotless(styles) { - if let MathFragment::Glyph(glyph) = &mut base { - glyph.make_dotless_form(ctx); - } - } + let cramped = style_cramped(); + let base = ctx.layout_into_fragment(&elem.base, base_styles.chain(&cramped))?; // Preserve class to preserve automatic spacing. let base_class = base.class(); @@ -32,26 +31,27 @@ pub fn layout_accent( let width = elem.size(styles).relative_to(base.width()); let Accent(c) = elem.accent; - let mut glyph = GlyphFragment::new(ctx, styles, c, elem.span()); + let mut glyph = ctx.layout_into_glyph(c, elem.span(), styles)?; + let flattened_base_height = value!(glyph.text, flattened_accent_base_height); + let accent_base_height = value!(glyph.text, accent_base_height); - // Try to replace accent glyph with flattened variant. - let flattened_base_height = scaled!(ctx, styles, flattened_accent_base_height); + // Try to replace the accent glyph with its flattened variant. + let flac = style_flac(); if base.ascent() > flattened_base_height { - glyph.make_flattened_accent_form(ctx); + glyph = ctx.layout_into_glyph(c, elem.span(), styles.chain(&flac))?; } // Forcing the accent to be at least as large as the base makes it too // wide in many case. - 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; + let short_fall = ACCENT_SHORT_FALL.at(glyph.text.size); + glyph.stretch_horizontal(ctx, width, short_fall); + let accent_attach = glyph.accent_attach; + let accent = glyph.into_frame(); // Descent is negative because the accent's ink bottom is above the // 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, styles, accent_base_height); let gap = -accent.descent() - base.ascent().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); diff --git a/crates/typst-layout/src/math/attach.rs b/crates/typst-layout/src/math/attach.rs index e1d7d7c9d..9dd7ea6ec 100644 --- a/crates/typst-layout/src/math/attach.rs +++ b/crates/typst-layout/src/math/attach.rs @@ -1,14 +1,16 @@ use typst_library::diag::SourceResult; -use typst_library::foundations::{Packed, StyleChain, SymbolElem}; +use typst_library::foundations::{Packed, StyleChain}; use typst_library::layout::{Abs, Axis, Corner, Frame, Point, Rel, Size}; use typst_library::math::{ AttachElem, EquationElem, LimitsElem, PrimesElem, ScriptsElem, StretchElem, }; +use typst_library::text::Font; +use typst_syntax::Span; use typst_utils::OptionExt; use super::{ - stretch_fragment, style_for_subscript, style_for_superscript, FrameFragment, Limits, - MathContext, MathFragment, + find_math_font, stretch_fragment, style_for_subscript, style_for_superscript, + FrameFragment, Limits, MathContext, MathFragment, }; macro_rules! measure { @@ -66,7 +68,6 @@ pub fn layout_attach( let relative_to_width = measure!(t, width).max(measure!(b, width)); stretch_fragment( ctx, - styles, &mut base, Some(Axis::X), Some(relative_to_width), @@ -103,14 +104,12 @@ pub fn layout_primes( 4 => '⁗', _ => unreachable!(), }; - let f = ctx.layout_into_fragment(&SymbolElem::packed(c), styles)?; + let f = ctx.layout_into_glyph(c, elem.span(), styles)?; ctx.push(f); } count => { // Custom amount of primes - let prime = ctx - .layout_into_fragment(&SymbolElem::packed('′'), styles)? - .into_frame(); + let prime = ctx.layout_into_glyph('′', elem.span(), 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()); @@ -174,18 +173,21 @@ fn layout_attachments( ) -> SourceResult<()> { let base_class = base.class(); + // TODO: should use base's font. + let font = find_math_font(ctx.engine, styles, Span::detached())?; + // Calculate the distance from the base's baseline to the superscripts' and // subscripts' baseline. let (tx_shift, bx_shift) = if [&tl, &tr, &bl, &br].iter().all(|e| e.is_none()) { (Abs::zero(), Abs::zero()) } else { - compute_script_shifts(ctx, styles, &base, [&tl, &tr, &bl, &br]) + compute_script_shifts(&font, styles, &base, [&tl, &tr, &bl, &br]) }; // Calculate the distance from the base's baseline to the top attachment's // and bottom attachment's baseline. let (t_shift, b_shift) = - compute_limit_shifts(ctx, styles, &base, [t.as_ref(), b.as_ref()]); + compute_limit_shifts(&font, styles, &base, [t.as_ref(), b.as_ref()]); // Calculate the final frame height. let ascent = base @@ -215,12 +217,11 @@ fn layout_attachments( // `space_after_script` is extra spacing that is at the start before each // pre-script, and at the end after each post-script (see the MathConstants // table in the OpenType MATH spec). - let space_after_script = scaled!(ctx, styles, space_after_script); + let space_after_script = constant!(font, styles, space_after_script); // Calculate the distance each pre-script extends to the left of the base's // width. let (tl_pre_width, bl_pre_width) = compute_pre_script_widths( - ctx, &base, [tl.as_ref(), bl.as_ref()], (tx_shift, bx_shift), @@ -231,7 +232,6 @@ fn layout_attachments( // base's width. Also calculate each post-script's kerning (we need this for // its position later). let ((tr_post_width, tr_kern), (br_post_width, br_kern)) = compute_post_script_widths( - ctx, &base, [tr.as_ref(), br.as_ref()], (tx_shift, bx_shift), @@ -287,14 +287,13 @@ fn layout_attachments( /// post-script's kerning value. The first tuple is for the post-superscript, /// and the second is for the post-subscript. fn compute_post_script_widths( - ctx: &MathContext, base: &MathFragment, [tr, br]: [Option<&MathFragment>; 2], (tr_shift, br_shift): (Abs, Abs), space_after_post_script: Abs, ) -> ((Abs, Abs), (Abs, Abs)) { let tr_values = tr.map_or_default(|tr| { - let kern = math_kern(ctx, base, tr, tr_shift, Corner::TopRight); + let kern = math_kern(base, tr, tr_shift, Corner::TopRight); (space_after_post_script + tr.width() + kern, kern) }); @@ -302,7 +301,7 @@ fn compute_post_script_widths( // need to shift the post-subscript left by the base's italic correction // (see the kerning algorithm as described in the OpenType MATH spec). let br_values = br.map_or_default(|br| { - let kern = math_kern(ctx, base, br, br_shift, Corner::BottomRight) + let kern = math_kern(base, br, br_shift, Corner::BottomRight) - base.italics_correction(); (space_after_post_script + br.width() + kern, kern) }); @@ -317,19 +316,18 @@ fn compute_post_script_widths( /// extends left of the base's width and the second being the distance the /// pre-subscript extends left of the base's width. fn compute_pre_script_widths( - ctx: &MathContext, base: &MathFragment, [tl, bl]: [Option<&MathFragment>; 2], (tl_shift, bl_shift): (Abs, Abs), space_before_pre_script: Abs, ) -> (Abs, Abs) { let tl_pre_width = tl.map_or_default(|tl| { - let kern = math_kern(ctx, base, tl, tl_shift, Corner::TopLeft); + let kern = math_kern(base, tl, tl_shift, Corner::TopLeft); space_before_pre_script + tl.width() + kern }); let bl_pre_width = bl.map_or_default(|bl| { - let kern = math_kern(ctx, base, bl, bl_shift, Corner::BottomLeft); + let kern = math_kern(base, bl, bl_shift, Corner::BottomLeft); space_before_pre_script + bl.width() + kern }); @@ -368,7 +366,7 @@ fn compute_limit_widths( /// Returns two lengths, the first being the distance to the upper-limit's /// baseline and the second being the distance to the lower-limit's baseline. fn compute_limit_shifts( - ctx: &MathContext, + font: &Font, styles: StyleChain, base: &MathFragment, [t, b]: [Option<&MathFragment>; 2], @@ -379,14 +377,14 @@ fn compute_limit_shifts( // MathConstants table in the OpenType MATH spec). let t_shift = t.map_or_default(|t| { - let upper_gap_min = scaled!(ctx, styles, upper_limit_gap_min); - let upper_rise_min = scaled!(ctx, styles, upper_limit_baseline_rise_min); + let upper_gap_min = constant!(font, styles, upper_limit_gap_min); + let upper_rise_min = constant!(font, styles, upper_limit_baseline_rise_min); base.ascent() + upper_rise_min.max(upper_gap_min + t.descent()) }); let b_shift = b.map_or_default(|b| { - let lower_gap_min = scaled!(ctx, styles, lower_limit_gap_min); - let lower_drop_min = scaled!(ctx, styles, lower_limit_baseline_drop_min); + let lower_gap_min = constant!(font, styles, lower_limit_gap_min); + let lower_drop_min = constant!(font, styles, lower_limit_baseline_drop_min); base.descent() + lower_drop_min.max(lower_gap_min + b.ascent()) }); @@ -397,25 +395,25 @@ fn compute_limit_shifts( /// Returns two lengths, the first being the distance to the superscripts' /// baseline and the second being the distance to the subscripts' baseline. fn compute_script_shifts( - ctx: &MathContext, + font: &Font, styles: StyleChain, base: &MathFragment, [tl, tr, bl, br]: [&Option; 4], ) -> (Abs, Abs) { let sup_shift_up = if EquationElem::cramped_in(styles) { - scaled!(ctx, styles, superscript_shift_up_cramped) + constant!(font, styles, superscript_shift_up_cramped) } else { - scaled!(ctx, styles, superscript_shift_up) + constant!(font, styles, superscript_shift_up) }; - let sup_bottom_min = scaled!(ctx, styles, superscript_bottom_min); + let sup_bottom_min = constant!(font, 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); + constant!(font, styles, superscript_bottom_max_with_subscript); + let sup_drop_max = constant!(font, styles, superscript_baseline_drop_max); + let gap_min = constant!(font, styles, sub_superscript_gap_min); + let sub_shift_down = constant!(font, styles, subscript_shift_down); + let sub_top_max = constant!(font, styles, subscript_top_max); + let sub_drop_min = constant!(font, styles, subscript_baseline_drop_min); let mut shift_up = Abs::zero(); let mut shift_down = Abs::zero(); @@ -467,13 +465,7 @@ fn compute_script_shifts( /// a negative value means shifting the script closer to the base. Requires the /// distance from the base's baseline to the script's baseline, as well as the /// script's corner (tl, tr, bl, br). -fn math_kern( - ctx: &MathContext, - base: &MathFragment, - script: &MathFragment, - shift: Abs, - pos: Corner, -) -> Abs { +fn math_kern(base: &MathFragment, script: &MathFragment, shift: Abs, pos: Corner) -> Abs { // This process is described under the MathKernInfo table in the OpenType // MATH spec. @@ -498,8 +490,8 @@ fn math_kern( // Calculate the sum of kerning values for each correction height. let summed_kern = |height| { - let base_kern = base.kern_at_height(ctx, pos, height); - let attach_kern = script.kern_at_height(ctx, pos.inv(), height); + let base_kern = base.kern_at_height(pos, height); + let attach_kern = script.kern_at_height(pos.inv(), height); base_kern + attach_kern }; diff --git a/crates/typst-layout/src/math/frac.rs b/crates/typst-layout/src/math/frac.rs index 6d3caac45..8aebe651b 100644 --- a/crates/typst-layout/src/math/frac.rs +++ b/crates/typst-layout/src/math/frac.rs @@ -7,7 +7,7 @@ use typst_library::visualize::{FixedStroke, Geometry}; use typst_syntax::Span; use super::{ - style_for_denominator, style_for_numerator, FrameFragment, GlyphFragment, + find_math_font, style_for_denominator, style_for_numerator, FrameFragment, MathContext, DELIM_SHORT_FALL, }; @@ -49,29 +49,30 @@ fn layout_frac_like( binom: bool, span: Span, ) -> SourceResult<()> { - let short_fall = DELIM_SHORT_FALL.resolve(styles); - let axis = scaled!(ctx, styles, axis_height); - let thickness = scaled!(ctx, styles, fraction_rule_thickness); - let shift_up = scaled!( - ctx, styles, + let font = find_math_font(ctx.engine, styles, span)?; + let axis = constant!(font, styles, axis_height); + let thickness = constant!(font, styles, fraction_rule_thickness); + let shift_up = constant!( + font, styles, text: fraction_numerator_shift_up, display: fraction_numerator_display_style_shift_up, ); - let shift_down = scaled!( - ctx, styles, + let shift_down = constant!( + font, styles, text: fraction_denominator_shift_down, display: fraction_denominator_display_style_shift_down, ); - let num_min = scaled!( - ctx, styles, + let num_min = constant!( + font, styles, text: fraction_numerator_gap_min, display: fraction_num_display_style_gap_min, ); - let denom_min = scaled!( - ctx, styles, + let denom_min = constant!( + font, styles, text: fraction_denominator_gap_min, display: fraction_denom_display_style_gap_min, ); + let short_fall = DELIM_SHORT_FALL.resolve(styles); let num_style = style_for_numerator(styles); let num = ctx.layout_into_frame(num, styles.chain(&num_style))?; @@ -109,14 +110,14 @@ fn layout_frac_like( frame.push_frame(denom_pos, denom); if binom { - let mut left = GlyphFragment::new(ctx, styles, '(', span) - .stretch_vertical(ctx, height, short_fall); - left.center_on_axis(ctx); + let mut left = ctx.layout_into_glyph('(', span, styles)?; + left.stretch_vertical(ctx, height, short_fall); + left.center_on_axis(); ctx.push(left); ctx.push(FrameFragment::new(styles, frame)); - let mut right = GlyphFragment::new(ctx, styles, ')', span) - .stretch_vertical(ctx, height, short_fall); - right.center_on_axis(ctx); + let mut right = ctx.layout_into_glyph(')', span, styles)?; + right.stretch_vertical(ctx, height, short_fall); + right.center_on_axis(); ctx.push(right); } else { frame.push( diff --git a/crates/typst-layout/src/math/fragment.rs b/crates/typst-layout/src/math/fragment.rs index 59858a9cb..d6f835bba 100644 --- a/crates/typst-layout/src/math/fragment.rs +++ b/crates/typst-layout/src/math/fragment.rs @@ -1,28 +1,34 @@ use std::fmt::{self, Debug, Formatter}; -use rustybuzz::Feature; -use ttf_parser::gsub::{AlternateSubstitution, SingleSubstitution, SubstitutionSubtable}; -use ttf_parser::opentype_layout::LayoutTable; -use ttf_parser::{GlyphId, Rect}; +use rustybuzz::{BufferFlags, UnicodeBuffer}; +use ttf_parser::math::{GlyphAssembly, GlyphConstruction, GlyphPart}; +use ttf_parser::GlyphId; +use typst_library::diag::{bail, warning, SourceResult}; use typst_library::foundations::StyleChain; use typst_library::introspection::Tag; use typst_library::layout::{ - Abs, Axis, Corner, Em, Frame, FrameItem, Point, Size, VAlignment, + Abs, Axes, Axis, Corner, Em, Frame, FrameItem, Point, Size, VAlignment, }; use typst_library::math::{EquationElem, MathSize}; -use typst_library::text::{Font, Glyph, Lang, Region, TextElem, TextItem}; -use typst_library::visualize::{FixedStroke, Paint}; +use typst_library::text::{ + families, features, language, variant, Font, Glyph, TextElem, TextItem, +}; +use typst_library::World; use typst_syntax::Span; -use typst_utils::default_math_class; +use typst_utils::{default_math_class, Get}; use unicode_math_class::MathClass; -use super::{stretch_glyph, MathContext, Scaled}; +use crate::inline::create_shape_plan; use crate::modifiers::{FrameModifiers, FrameModify}; +use super::MathContext; + +/// Maximum number of times extenders can be repeated. +const MAX_REPEATS: usize = 1024; + #[derive(Debug, Clone)] pub enum MathFragment { Glyph(GlyphFragment), - Variant(VariantFragment), Frame(FrameFragment), Spacing(Abs, bool), Space(Abs), @@ -33,13 +39,18 @@ pub enum MathFragment { impl MathFragment { pub fn size(&self) -> Size { - Size::new(self.width(), self.height()) + match self { + Self::Glyph(glyph) => glyph.size, + Self::Frame(fragment) => fragment.frame.size(), + Self::Spacing(amount, _) => Size::with_x(*amount), + Self::Space(amount) => Size::with_x(*amount), + _ => Size::zero(), + } } pub fn width(&self) -> Abs { match self { - Self::Glyph(glyph) => glyph.width, - Self::Variant(variant) => variant.frame.width(), + Self::Glyph(glyph) => glyph.size.x, Self::Frame(fragment) => fragment.frame.width(), Self::Spacing(amount, _) => *amount, Self::Space(amount) => *amount, @@ -49,8 +60,7 @@ impl MathFragment { pub fn height(&self) -> Abs { match self { - Self::Glyph(glyph) => glyph.height(), - Self::Variant(variant) => variant.frame.height(), + Self::Glyph(glyph) => glyph.size.y, Self::Frame(fragment) => fragment.frame.height(), _ => Abs::zero(), } @@ -58,17 +68,15 @@ impl MathFragment { pub fn ascent(&self) -> Abs { match self { - Self::Glyph(glyph) => glyph.ascent, - Self::Variant(variant) => variant.frame.ascent(), - Self::Frame(fragment) => fragment.frame.baseline(), + Self::Glyph(glyph) => glyph.ascent(), + Self::Frame(fragment) => fragment.frame.ascent(), _ => Abs::zero(), } } pub fn descent(&self) -> Abs { match self { - Self::Glyph(glyph) => glyph.descent, - Self::Variant(variant) => variant.frame.descent(), + Self::Glyph(glyph) => glyph.descent(), Self::Frame(fragment) => fragment.frame.descent(), _ => Abs::zero(), } @@ -85,7 +93,6 @@ impl MathFragment { pub fn class(&self) -> MathClass { match self { Self::Glyph(glyph) => glyph.class, - Self::Variant(variant) => variant.class, Self::Frame(fragment) => fragment.class, Self::Spacing(_, _) => MathClass::Space, Self::Space(_) => MathClass::Space, @@ -98,7 +105,6 @@ impl MathFragment { pub fn math_size(&self) -> Option { match self { Self::Glyph(glyph) => Some(glyph.math_size), - Self::Variant(variant) => Some(variant.math_size), Self::Frame(fragment) => Some(fragment.math_size), _ => None, } @@ -106,8 +112,7 @@ impl MathFragment { pub fn font_size(&self) -> Option { match self { - Self::Glyph(glyph) => Some(glyph.font_size), - Self::Variant(variant) => Some(variant.font_size), + Self::Glyph(glyph) => Some(glyph.text.size), Self::Frame(fragment) => Some(fragment.font_size), _ => None, } @@ -116,7 +121,6 @@ impl MathFragment { pub fn set_class(&mut self, class: MathClass) { match self { Self::Glyph(glyph) => glyph.class = class, - Self::Variant(variant) => variant.class = class, Self::Frame(fragment) => fragment.class = class, _ => {} } @@ -125,7 +129,6 @@ impl MathFragment { pub fn set_limits(&mut self, limits: Limits) { match self { Self::Glyph(glyph) => glyph.limits = limits, - Self::Variant(variant) => variant.limits = limits, Self::Frame(fragment) => fragment.limits = limits, _ => {} } @@ -149,7 +152,6 @@ impl MathFragment { pub fn is_text_like(&self) -> bool { match self { Self::Glyph(glyph) => !glyph.extended_shape, - Self::Variant(variant) => !variant.extended_shape, MathFragment::Frame(frame) => frame.text_like, _ => false, } @@ -158,7 +160,6 @@ impl MathFragment { pub fn italics_correction(&self) -> Abs { match self { Self::Glyph(glyph) => glyph.italics_correction, - Self::Variant(variant) => variant.italics_correction, Self::Frame(fragment) => fragment.italics_correction, _ => Abs::zero(), } @@ -167,7 +168,6 @@ impl MathFragment { pub fn accent_attach(&self) -> Abs { match self { Self::Glyph(glyph) => glyph.accent_attach, - Self::Variant(variant) => variant.accent_attach, Self::Frame(fragment) => fragment.accent_attach, _ => self.width() / 2.0, } @@ -176,7 +176,6 @@ impl MathFragment { pub fn into_frame(self) -> Frame { match self { Self::Glyph(glyph) => glyph.into_frame(), - Self::Variant(variant) => variant.frame, Self::Frame(fragment) => fragment.frame, Self::Tag(tag) => { let mut frame = Frame::soft(Size::zero()); @@ -190,7 +189,6 @@ impl MathFragment { pub fn limits(&self) -> Limits { match self { MathFragment::Glyph(glyph) => glyph.limits, - MathFragment::Variant(variant) => variant.limits, MathFragment::Frame(fragment) => fragment.limits, _ => Limits::Never, } @@ -198,11 +196,31 @@ impl MathFragment { /// If no kern table is provided for a corner, a kerning amount of zero is /// assumed. - pub fn kern_at_height(&self, ctx: &MathContext, corner: Corner, height: Abs) -> Abs { + pub fn kern_at_height(&self, corner: Corner, height: Abs) -> Abs { match self { Self::Glyph(glyph) => { - kern_at_height(ctx, glyph.font_size, glyph.id, corner, height) - .unwrap_or_default() + // For glyph assemblies we pick either the start or end glyph + // depending on the corner. + let is_vertical = + glyph.text.glyphs.iter().any(|glyph| glyph.y_advance != Em::zero()); + let glyph_index = match (is_vertical, corner) { + (true, Corner::TopLeft | Corner::TopRight) => { + glyph.text.glyphs.len() - 1 + } + (false, Corner::TopRight | Corner::BottomRight) => { + glyph.text.glyphs.len() - 1 + } + _ => 0, + }; + + kern_at_height( + &glyph.text.font, + GlyphId(glyph.text.glyphs[glyph_index].id), + corner, + Em::from_length(height, glyph.text.size), + ) + .unwrap_or_default() + .at(glyph.text.size) } _ => Abs::zero(), } @@ -215,12 +233,6 @@ impl From for MathFragment { } } -impl From for MathFragment { - fn from(variant: VariantFragment) -> Self { - Self::Variant(variant) - } -} - impl From for MathFragment { fn from(fragment: FrameFragment) -> Self { Self::Frame(fragment) @@ -229,266 +241,314 @@ impl From for MathFragment { #[derive(Clone)] pub struct GlyphFragment { - pub id: GlyphId, - pub c: char, - pub font: Font, - pub lang: Lang, - pub region: Option, - pub fill: Paint, - pub stroke: Option, - pub shift: Abs, - pub width: Abs, - pub ascent: Abs, - pub descent: Abs, + // Text stuff. + pub text: TextItem, + pub base_id: GlyphId, + // Math stuff. + pub size: Size, + pub baseline: Option, pub italics_correction: Abs, pub accent_attach: Abs, - pub font_size: Abs, - pub class: MathClass, pub math_size: MathSize, - pub span: Span, - pub modifiers: FrameModifiers, + pub class: MathClass, pub limits: Limits, pub extended_shape: bool, + pub mid_stretched: Option, + // External frame stuff. + pub modifiers: FrameModifiers, + pub shift: Abs, + pub align: Abs, } impl GlyphFragment { - 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, styles, c, id, span) - } - - pub fn try_new( + pub fn new( ctx: &MathContext, styles: StyleChain, - c: char, + text: &str, span: Span, - ) -> Option { - let id = ctx.ttf.glyph_index(c)?; - let id = Self::adjust_glyph_index(ctx, id); - Some(Self::with_id(ctx, styles, c, id, span)) - } + ) -> SourceResult> { + let families = families(styles); + let variant = variant(styles); + let fallback = TextElem::fallback_in(styles); + let end = text.char_indices().nth(1).map(|(i, _)| i).unwrap_or(text.len()); - pub fn with_id( - ctx: &MathContext, - styles: StyleChain, - c: char, - id: GlyphId, - span: Span, - ) -> Self { + // Find the next available family. + let world = ctx.engine.world; + let book = world.book(); + let mut selection = None; + for family in families { + selection = book + .select(family.as_str(), variant) + .and_then(|id| world.font(id)) + .filter(|font| { + font.ttf().tables().math.and_then(|math| math.constants).is_some() + }) + .filter(|_| family.covers().is_none_or(|cov| cov.is_match(&text[..end]))); + if selection.is_some() { + break; + } + } + + // Do font fallback if the families are exhausted and fallback is enabled. + if selection.is_none() && fallback { + selection = book + .select_fallback(None, variant, text) + .and_then(|id| world.font(id)) + .filter(|font| { + font.ttf().tables().math.and_then(|math| math.constants).is_some() + }); + } + + // Error out if no math font could be found at all. + let Some(font) = selection else { + bail!(span, "current font does not support math"); + }; + + let mut buffer = UnicodeBuffer::new(); + buffer.push_str(text); + buffer.set_language(language(styles)); + // TODO: Bug in rustybuzz? rustybuzz::script::SCRIPT_MATH does not work. + // i.e. ssty is not applied + buffer.set_script( + rustybuzz::Script::from_iso15924_tag(ttf_parser::Tag::from_bytes(b"math")) + .unwrap(), + ); + buffer.set_direction(rustybuzz::Direction::LeftToRight); + buffer.set_flags(BufferFlags::REMOVE_DEFAULT_IGNORABLES); + + let features = features(styles); + let plan = create_shape_plan( + &font, + buffer.direction(), + buffer.script(), + buffer.language().as_ref(), + &features, + ); + + let buffer = rustybuzz::shape_with_plan(font.rusty(), &plan, buffer); + + match buffer.len() { + 0 => return Ok(None), + 1 => {} + _ => unreachable!(), + } + + let info = buffer.glyph_infos()[0]; + let pos = buffer.glyph_positions()[0]; + + let cluster = info.cluster as usize; + let c = text[cluster..].chars().next().unwrap(); + let limits = Limits::for_char(c); let class = EquationElem::class_in(styles) .or_else(|| default_math_class(c)) .unwrap_or(MathClass::Normal); - let mut fragment = Self { - id, - c, - font: ctx.font.clone(), - lang: TextElem::lang_in(styles), - region: TextElem::region_in(styles), + let text = TextItem { + font: font.clone(), + size: TextElem::size_in(styles), fill: TextElem::fill_in(styles).as_decoration(), stroke: TextElem::stroke_in(styles).map(|s| s.unwrap_or_default()), - shift: TextElem::baseline_in(styles), - font_size: TextElem::size_in(styles), + lang: TextElem::lang_in(styles), + region: TextElem::region_in(styles), + text: text.into(), + glyphs: vec![Glyph { + id: info.glyph_id as u16, + x_advance: font.to_em(pos.x_advance), + x_offset: Em::zero(), + y_advance: Em::zero(), + y_offset: Em::zero(), + range: 0..text.len() as u16, + span: (span, 0), + }], + }; + + let mut fragment = Self { + text, + base_id: GlyphId(info.glyph_id as u16), + // Math math_size: EquationElem::size_in(styles), - width: Abs::zero(), - ascent: Abs::zero(), - descent: Abs::zero(), - limits: Limits::for_char(c), + class, + limits, + mid_stretched: None, + // Math in need of updating. + extended_shape: false, italics_correction: Abs::zero(), accent_attach: Abs::zero(), - class, - span, + size: Size::zero(), + baseline: None, + // Misc + align: Abs::zero(), + shift: TextElem::baseline_in(styles), modifiers: FrameModifiers::get_in(styles), - extended_shape: false, }; - fragment.set_id(ctx, id); - fragment - } - - /// Apply GSUB substitutions. - fn adjust_glyph_index(ctx: &MathContext, id: GlyphId) -> GlyphId { - if let Some(glyphwise_tables) = &ctx.glyphwise_tables { - glyphwise_tables.iter().fold(id, |id, table| table.apply(id)) - } else { - id - } + fragment.update_glyph(); + Ok(Some(fragment)) } /// Sets element id and boxes in appropriate way without changing other /// 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, self.font_size).unwrap_or_default(); - let bbox = ctx.ttf.glyph_bounding_box(id).unwrap_or(Rect { - x_min: 0, - y_min: 0, - x_max: 0, - y_max: 0, - }); + pub fn update_glyph(&mut self) { + let id = GlyphId(self.text.glyphs[0].id); - 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); - - let extended_shape = is_extended_shape(ctx, id); + let extended_shape = is_extended_shape(&self.text.font, id); + let italics = italics_correction(&self.text.font, id).unwrap_or_default(); + let width = self.text.width(); if !extended_shape { - width += italics; + self.text.glyphs[0].x_advance += italics; } + let italics = italics.at(self.text.size); - self.id = id; - self.width = width; - self.ascent = bbox.y_max.scaled(ctx, self.font_size); - self.descent = -bbox.y_min.scaled(ctx, self.font_size); + let (ascent, descent) = + ascent_descent(&self.text.font, id).unwrap_or((Em::zero(), Em::zero())); + + let accent_attach = accent_attach(&self.text.font, id) + .map(|x| x.at(self.text.size)) + .unwrap_or((width + italics) / 2.0); + + self.baseline = Some(ascent.at(self.text.size)); + self.size = Size::new( + self.text.width(), + ascent.at(self.text.size) + descent.at(self.text.size), + ); self.italics_correction = italics; self.accent_attach = accent_attach; self.extended_shape = extended_shape; } - pub fn height(&self) -> Abs { - self.ascent + self.descent + // Reset a GlyphFragment's text field and math properties back to its + // base_id's. This is used to return a glyph to its unstretched state. + pub fn reset_glyph(&mut self) { + self.align = Abs::zero(); + self.text.glyphs = vec![Glyph { + id: self.base_id.0, + x_advance: self.text.font.advance(self.base_id.0).unwrap_or_default(), + ..self.text.glyphs[0].clone() + }]; + self.update_glyph(); } - pub fn into_variant(self) -> VariantFragment { - VariantFragment { - c: self.c, - 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, - extended_shape: self.extended_shape, - frame: self.into_frame(), - mid_stretched: None, - } + pub fn baseline(&self) -> Abs { + self.ascent() + } + + /// The distance from the baseline to the top of the frame. + pub fn ascent(&self) -> Abs { + self.baseline.unwrap_or(self.size.y) + } + + /// The distance from the baseline to the bottom of the frame. + pub fn descent(&self) -> Abs { + self.size.y - self.ascent() } pub fn into_frame(self) -> Frame { - let item = TextItem { - font: self.font.clone(), - size: self.font_size, - fill: self.fill, - stroke: self.stroke, - lang: self.lang, - region: self.region, - text: self.c.into(), - glyphs: vec![Glyph { - id: self.id.0, - x_advance: Em::from_length(self.width, self.font_size), - x_offset: Em::zero(), - range: 0..self.c.len_utf8() as u16, - span: (self.span, 0), - }], - }; - let size = Size::new(self.width, self.ascent + self.descent); - let mut frame = Frame::soft(size); - frame.set_baseline(self.ascent); - frame.push(Point::with_y(self.ascent + self.shift), FrameItem::Text(item)); + let mut frame = Frame::soft(self.size); + frame.set_baseline(self.baseline()); + frame.push( + Point::with_y(self.ascent() + self.shift + self.align), + FrameItem::Text(self.text), + ); frame.modify(&self.modifiers); frame } - pub fn make_script_size(&mut self, ctx: &MathContext) { - let alt_id = - ctx.ssty_table.as_ref().and_then(|ssty| ssty.try_apply(self.id, None)); - if let Some(alt_id) = alt_id { - self.set_id(ctx, alt_id); - } - } - - pub fn make_script_script_size(&mut self, ctx: &MathContext) { - let alt_id = ctx.ssty_table.as_ref().and_then(|ssty| { - // We explicitly request to apply the alternate set with value 1, - // as opposed to the default value in ssty, as the former - // corresponds to second level scripts and the latter corresponds - // to first level scripts. - ssty.try_apply(self.id, Some(1)) - .or_else(|| ssty.try_apply(self.id, None)) - }); - if let Some(alt_id) = alt_id { - self.set_id(ctx, alt_id); - } - } - - pub fn make_dotless_form(&mut self, ctx: &MathContext) { - let alt_id = - ctx.dtls_table.as_ref().and_then(|dtls| dtls.try_apply(self.id, None)); - if let Some(alt_id) = alt_id { - self.set_id(ctx, alt_id); - } - } - - pub fn make_flattened_accent_form(&mut self, ctx: &MathContext) { - let alt_id = - ctx.flac_table.as_ref().and_then(|flac| flac.try_apply(self.id, None)); - if let Some(alt_id) = alt_id { - self.set_id(ctx, alt_id); - } - } - /// Try to stretch a glyph to a desired height. pub fn stretch_vertical( - self, + &mut self, ctx: &mut MathContext, height: Abs, short_fall: Abs, - ) -> VariantFragment { - stretch_glyph(ctx, self, height, short_fall, Axis::Y) + ) { + self.stretch(ctx, height, short_fall, Axis::Y) } /// Try to stretch a glyph to a desired width. pub fn stretch_horizontal( - self, + &mut self, ctx: &mut MathContext, width: Abs, short_fall: Abs, - ) -> VariantFragment { - stretch_glyph(ctx, self, width, short_fall, Axis::X) + ) { + self.stretch(ctx, width, short_fall, Axis::X) + } + + /// Try to stretch a glyph to a desired width or height. + /// + /// The resulting frame may not have the exact desired width or height. + pub fn stretch( + &mut self, + ctx: &mut MathContext, + target: Abs, + short_fall: Abs, + axis: Axis, + ) { + self.reset_glyph(); + + // If the base glyph is good enough, use it. + let mut advance = self.size.get(axis); + if axis == Axis::X && !self.extended_shape { + // For consistency, we subtract the italics correction from the + // glyph's width if it was added in `update_glyph`. + advance -= self.italics_correction; + } + let short_target = target - short_fall; + if short_target <= advance { + return; + } + + let id = GlyphId(self.text.glyphs[0].id); + let font = self.text.font.clone(); + let Some(construction) = glyph_construction(&font, id, axis) else { return }; + + // Search for a pre-made variant with a good advance. + let mut best_id = id; + let mut best_advance = advance; + for variant in construction.variants { + best_id = variant.variant_glyph; + best_advance = + self.text.font.to_em(variant.advance_measurement).at(self.text.size); + if short_target <= best_advance { + break; + } + } + + // This is either good or the best we've got. + if short_target <= best_advance || construction.assembly.is_none() { + self.text.glyphs[0].id = best_id.0; + self.text.glyphs[0].x_advance = + self.text.font.advance(best_id.0).unwrap_or_default(); + self.update_glyph(); + return; + } + + // Assemble from parts. + let assembly = construction.assembly.unwrap(); + let min_overlap = min_connector_overlap(&self.text.font) + .unwrap_or_default() + .at(self.text.size); + assemble(ctx, self, assembly, min_overlap, target, axis); + } + + /// Vertically adjust the fragment's frame so that it is centered + /// on the axis. + pub fn center_on_axis(&mut self) { + self.align_on_axis(VAlignment::Horizon); + } + + /// Vertically adjust the fragment's frame so that it is aligned + /// to the given alignment on the axis. + pub fn align_on_axis(&mut self, align: VAlignment) { + let h = self.size.y; + let axis = value!(self.text, axis_height); + self.align += self.baseline(); + self.baseline = Some(align.inv().position(h + axis * 2.0)); + self.align -= self.baseline(); } } impl Debug for GlyphFragment { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "GlyphFragment({:?})", self.c) - } -} - -#[derive(Clone)] -pub struct VariantFragment { - pub c: char, - pub italics_correction: Abs, - pub accent_attach: Abs, - pub frame: Frame, - pub font_size: Abs, - pub class: MathClass, - pub math_size: MathSize, - pub span: Span, - pub limits: Limits, - pub mid_stretched: Option, - pub extended_shape: bool, -} - -impl VariantFragment { - /// Vertically adjust the fragment's frame so that it is centered - /// on the axis. - pub fn center_on_axis(&mut self, ctx: &MathContext) { - self.align_on_axis(ctx, VAlignment::Horizon) - } - - /// Vertically adjust the fragment's frame so that it is aligned - /// to the given alignment on the axis. - pub fn align_on_axis(&mut self, ctx: &MathContext, align: VAlignment) { - let h = self.frame.height(); - let axis = ctx.constants.axis_height().scaled(ctx, self.font_size); - self.frame.set_baseline(align.inv().position(h + axis * 2.0)); - } -} - -impl Debug for VariantFragment { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "VariantFragment({:?})", self.c) + write!(f, "GlyphFragment({:?})", self.text.text) } } @@ -559,46 +619,47 @@ impl FrameFragment { } } +fn ascent_descent(font: &Font, id: GlyphId) -> Option<(Em, Em)> { + let bbox = font.ttf().glyph_bounding_box(id)?; + Some((font.to_em(bbox.y_max), -font.to_em(bbox.y_min))) +} + /// Look up the italics correction for a glyph. -fn italics_correction(ctx: &MathContext, id: GlyphId, font_size: Abs) -> Option { - Some( - ctx.table - .glyph_info? - .italic_corrections? - .get(id)? - .scaled(ctx, font_size), - ) +fn italics_correction(font: &Font, id: GlyphId) -> Option { + font.ttf() + .tables() + .math? + .glyph_info? + .italic_corrections? + .get(id) + .map(|value| font.to_em(value.value)) } /// Loop up the top accent attachment position for a glyph. -fn accent_attach(ctx: &MathContext, id: GlyphId, font_size: Abs) -> Option { - Some( - ctx.table - .glyph_info? - .top_accent_attachments? - .get(id)? - .scaled(ctx, font_size), - ) +fn accent_attach(font: &Font, id: GlyphId) -> Option { + font.ttf() + .tables() + .math? + .glyph_info? + .top_accent_attachments? + .get(id) + .map(|value| font.to_em(value.value)) } /// Look up whether a glyph is an extended shape. -fn is_extended_shape(ctx: &MathContext, id: GlyphId) -> bool { - ctx.table - .glyph_info - .and_then(|info| info.extended_shapes) - .and_then(|info| info.get(id)) +fn is_extended_shape(font: &Font, id: GlyphId) -> bool { + font.ttf() + .tables() + .math + .and_then(|math| math.glyph_info) + .and_then(|glyph_info| glyph_info.extended_shapes) + .and_then(|coverage| coverage.get(id)) .is_some() } /// Look up a kerning value at a specific corner and height. -fn kern_at_height( - ctx: &MathContext, - font_size: Abs, - id: GlyphId, - corner: Corner, - height: Abs, -) -> Option { - let kerns = ctx.table.glyph_info?.kern_infos?.get(id)?; +fn kern_at_height(font: &Font, id: GlyphId, corner: Corner, height: Em) -> Option { + let kerns = font.ttf().tables().math?.glyph_info?.kern_infos?.get(id)?; let kern = match corner { Corner::TopLeft => kerns.top_left, Corner::TopRight => kerns.top_right, @@ -607,11 +668,182 @@ fn kern_at_height( }?; let mut i = 0; - while i < kern.count() && height > kern.height(i)?.scaled(ctx, font_size) { + while i < kern.count() && height > font.to_em(kern.height(i)?.value) { i += 1; } - Some(kern.kern(i)?.scaled(ctx, font_size)) + Some(font.to_em(kern.kern(i)?.value)) +} + +pub fn stretch_axes(font: &Font, id: GlyphId) -> Axes { + let horizontal = font + .ttf() + .tables() + .math + .and_then(|math| math.variants) + .and_then(|variants| variants.horizontal_constructions.get(id)) + .is_some(); + let vertical = font + .ttf() + .tables() + .math + .and_then(|math| math.variants) + .and_then(|variants| variants.vertical_constructions.get(id)) + .is_some(); + + Axes::new(horizontal, vertical) +} + +fn min_connector_overlap(font: &Font) -> Option { + font.ttf() + .tables() + .math? + .variants + .map(|variants| font.to_em(variants.min_connector_overlap)) +} + +fn glyph_construction(font: &Font, id: GlyphId, axis: Axis) -> Option { + font.ttf() + .tables() + .math? + .variants + .map(|variants| match axis { + Axis::X => variants.horizontal_constructions, + Axis::Y => variants.vertical_constructions, + }) + .and_then(|constructions| constructions.get(id)) +} + +/// Assemble a glyph from parts. +fn assemble( + ctx: &mut MathContext, + base: &mut GlyphFragment, + assembly: GlyphAssembly, + min_overlap: Abs, + target: Abs, + axis: Axis, +) { + // Determine the number of times the extenders need to be repeated as well + // as a ratio specifying how much to spread the parts apart + // (0 = maximal overlap, 1 = minimal overlap). + let mut full; + let mut ratio; + let mut repeat = 0; + loop { + full = Abs::zero(); + ratio = 0.0; + + let mut parts = parts(assembly, repeat).peekable(); + let mut growable = Abs::zero(); + + while let Some(part) = parts.next() { + let mut advance = base.text.font.to_em(part.full_advance).at(base.text.size); + if let Some(next) = parts.peek() { + let max_overlap = base + .text + .font + .to_em(part.end_connector_length.min(next.start_connector_length)) + .at(base.text.size); + if max_overlap < min_overlap { + // This condition happening is indicative of a bug in the + // font. + ctx.engine.sink.warn(warning!( + base.text.glyphs[0].span.0, + "glyph has assembly parts with overlap less than minConnectorOverlap"; + hint: "its rendering may appear broken - this is probably a font bug"; + hint: "please file an issue at https://github.com/typst/typst/issues" + )); + } + + advance -= max_overlap; + growable += max_overlap - min_overlap; + } + + full += advance; + } + + if full < target { + let delta = target - full; + ratio = (delta / growable).min(1.0); + full += ratio * growable; + } + + if target <= full || repeat >= MAX_REPEATS { + break; + } + + repeat += 1; + } + + let mut glyphs = vec![]; + let mut parts = parts(assembly, repeat).peekable(); + while let Some(part) = parts.next() { + let mut advance = base.text.font.to_em(part.full_advance).at(base.text.size); + if let Some(next) = parts.peek() { + let max_overlap = base + .text + .font + .to_em(part.end_connector_length.min(next.start_connector_length)) + .at(base.text.size); + advance -= max_overlap; + advance += ratio * (max_overlap - min_overlap); + } + let (x, y) = match axis { + Axis::X => (Em::from_length(advance, base.text.size), Em::zero()), + Axis::Y => (Em::zero(), Em::from_length(advance, base.text.size)), + }; + glyphs.push(Glyph { + id: part.glyph_id.0, + x_advance: x, + x_offset: Em::zero(), + y_advance: y, + y_offset: Em::zero(), + ..base.text.glyphs[0].clone() + }); + } + + match axis { + Axis::X => base.size.x = full, + Axis::Y => { + base.baseline = None; + base.size.y = full; + base.size.x = glyphs + .iter() + .map(|glyph| base.text.font.advance(glyph.id).unwrap_or_default()) + .max() + .unwrap_or_default() + .at(base.text.size); + } + } + + base.text.glyphs = glyphs; + base.italics_correction = base + .text + .font + .to_em(assembly.italics_correction.value) + .at(base.text.size); + if axis == Axis::X { + base.accent_attach = full / 2.0; + } + base.mid_stretched = None; + base.extended_shape = true; +} + +/// Return an iterator over the assembly's parts with extenders repeated the +/// specified number of times. +fn parts(assembly: GlyphAssembly, repeat: usize) -> impl Iterator + '_ { + assembly.parts.into_iter().flat_map(move |part| { + let count = if part.part_flags.extender() { repeat } else { 1 }; + std::iter::repeat_n(part, count) + }) +} + +pub fn has_dtls_feat(font: &Font) -> bool { + font.ttf() + .tables() + .gsub + .and_then(|gsub| gsub.features.index(ttf_parser::Tag::from_bytes(b"dtls"))) + .is_some() } /// Describes in which situation a frame should use limits for attachments. @@ -664,56 +896,3 @@ impl Limits { fn is_integral_char(c: char) -> bool { ('∫'..='∳').contains(&c) || ('⨋'..='⨜').contains(&c) } - -/// An OpenType substitution table that is applicable to glyph-wise substitutions. -pub enum GlyphwiseSubsts<'a> { - Single(SingleSubstitution<'a>), - Alternate(AlternateSubstitution<'a>, u32), -} - -impl<'a> GlyphwiseSubsts<'a> { - pub fn new(gsub: Option>, feature: Feature) -> Option { - let gsub = gsub?; - let table = gsub - .features - .find(feature.tag) - .and_then(|feature| feature.lookup_indices.get(0)) - .and_then(|index| gsub.lookups.get(index))?; - let table = table.subtables.get::(0)?; - match table { - SubstitutionSubtable::Single(single_glyphs) => { - Some(Self::Single(single_glyphs)) - } - SubstitutionSubtable::Alternate(alt_glyphs) => { - Some(Self::Alternate(alt_glyphs, feature.value)) - } - _ => None, - } - } - - pub fn try_apply( - &self, - glyph_id: GlyphId, - alt_value: Option, - ) -> Option { - match self { - Self::Single(single) => match single { - SingleSubstitution::Format1 { coverage, delta } => coverage - .get(glyph_id) - .map(|_| GlyphId(glyph_id.0.wrapping_add(*delta as u16))), - SingleSubstitution::Format2 { coverage, substitutes } => { - coverage.get(glyph_id).and_then(|idx| substitutes.get(idx)) - } - }, - Self::Alternate(alternate, value) => alternate - .coverage - .get(glyph_id) - .and_then(|idx| alternate.alternate_sets.get(idx)) - .and_then(|set| set.alternates.get(alt_value.unwrap_or(*value) as u16)), - } - } - - pub fn apply(&self, glyph_id: GlyphId) -> GlyphId { - self.try_apply(glyph_id, None).unwrap_or(glyph_id) - } -} diff --git a/crates/typst-layout/src/math/lr.rs b/crates/typst-layout/src/math/lr.rs index bf8235411..544193fca 100644 --- a/crates/typst-layout/src/math/lr.rs +++ b/crates/typst-layout/src/math/lr.rs @@ -5,7 +5,9 @@ use typst_library::math::{EquationElem, LrElem, MidElem}; use typst_utils::SliceExt; use unicode_math_class::MathClass; -use super::{stretch_fragment, MathContext, MathFragment, DELIM_SHORT_FALL}; +use super::{ + find_math_font, stretch_fragment, MathContext, MathFragment, DELIM_SHORT_FALL, +}; /// Lays out an [`LrElem`]. #[typst_macros::time(name = "math.lr", span = elem.span())] @@ -33,7 +35,8 @@ pub fn layout_lr( let (start_idx, end_idx) = fragments.split_prefix_suffix(|f| f.is_ignorant()); let inner_fragments = &mut fragments[start_idx..end_idx]; - let axis = scaled!(ctx, styles, axis_height); + let font = find_math_font(ctx.engine, styles, elem.span())?; + let axis = constant!(font, styles, axis_height); let max_extent = inner_fragments .iter() .map(|fragment| (fragment.ascent() - axis).max(fragment.descent() + axis)) @@ -45,20 +48,20 @@ pub fn layout_lr( // Scale up fragments at both ends. match inner_fragments { - [one] => scale(ctx, styles, one, relative_to, height, None), + [one] => scale(ctx, one, relative_to, height, None), [first, .., last] => { - scale(ctx, styles, first, relative_to, height, Some(MathClass::Opening)); - scale(ctx, styles, last, relative_to, height, Some(MathClass::Closing)); + scale(ctx, first, relative_to, height, Some(MathClass::Opening)); + scale(ctx, last, relative_to, height, Some(MathClass::Closing)); } _ => {} } - // Handle MathFragment::Variant fragments that should be scaled up. + // Handle MathFragment::Glyph fragments that should be scaled up. for fragment in inner_fragments.iter_mut() { - if let MathFragment::Variant(ref mut variant) = fragment { - if variant.mid_stretched == Some(false) { - variant.mid_stretched = Some(true); - scale(ctx, styles, fragment, relative_to, height, Some(MathClass::Large)); + if let MathFragment::Glyph(ref mut glyph) = fragment { + if glyph.mid_stretched == Some(false) { + glyph.mid_stretched = Some(true); + scale(ctx, fragment, relative_to, height, Some(MathClass::Large)); } } } @@ -95,18 +98,9 @@ pub fn layout_mid( let mut fragments = ctx.layout_into_fragments(&elem.body, styles)?; for fragment in &mut fragments { - match fragment { - MathFragment::Glyph(glyph) => { - let mut new = glyph.clone().into_variant(); - new.mid_stretched = Some(false); - new.class = MathClass::Fence; - *fragment = MathFragment::Variant(new); - } - MathFragment::Variant(variant) => { - variant.mid_stretched = Some(false); - variant.class = MathClass::Fence; - } - _ => {} + if let MathFragment::Glyph(ref mut glyph) = fragment { + glyph.mid_stretched = Some(false); + glyph.class = MathClass::Fence; } } @@ -117,7 +111,6 @@ pub fn layout_mid( /// Scale a math fragment to a height. fn scale( ctx: &mut MathContext, - styles: StyleChain, fragment: &mut MathFragment, relative_to: Abs, height: Rel, @@ -132,7 +125,6 @@ fn scale( let short_fall = DELIM_SHORT_FALL.at(fragment.font_size().unwrap_or_default()); stretch_fragment( ctx, - styles, fragment, Some(Axis::Y), Some(relative_to), diff --git a/crates/typst-layout/src/math/mat.rs b/crates/typst-layout/src/math/mat.rs index d678f8658..5f3aa0570 100644 --- a/crates/typst-layout/src/math/mat.rs +++ b/crates/typst-layout/src/math/mat.rs @@ -1,5 +1,5 @@ use typst_library::diag::{bail, warning, SourceResult}; -use typst_library::foundations::{Content, Packed, Resolve, StyleChain}; +use typst_library::foundations::{Content, Packed, Resolve, StyleChain, SymbolElem}; use typst_library::layout::{ Abs, Axes, Em, FixedAlignment, Frame, FrameItem, Point, Ratio, Rel, Size, }; @@ -9,8 +9,8 @@ use typst_library::visualize::{FillRule, FixedStroke, Geometry, LineCap, Shape}; use typst_syntax::Span; use super::{ - alignments, delimiter_alignment, style_for_denominator, AlignmentResult, - FrameFragment, GlyphFragment, LeftRightAlternator, MathContext, DELIM_SHORT_FALL, + alignments, delimiter_alignment, find_math_font, style_for_denominator, + AlignmentResult, FrameFragment, LeftRightAlternator, MathContext, DELIM_SHORT_FALL, }; const VERTICAL_PADDING: Ratio = Ratio::new(0.1); @@ -184,7 +184,7 @@ fn layout_body( // to ensure that normal matrices are aligned with others unless they are // way too big. let paren = - GlyphFragment::new(ctx, styles.chain(&denom_style), '(', Span::detached()); + ctx.layout_into_fragment(&SymbolElem::packed('('), styles.chain(&denom_style))?; for (column, col) in columns.iter().zip(&mut cols) { for (cell, (ascent, descent)) in column.iter().zip(&mut heights) { @@ -202,8 +202,8 @@ fn layout_body( )); } - ascent.set_max(cell.ascent().max(paren.ascent)); - descent.set_max(cell.descent().max(paren.descent)); + ascent.set_max(cell.ascent().max(paren.ascent())); + descent.set_max(cell.descent().max(paren.descent())); col.push(cell); } @@ -307,24 +307,25 @@ fn layout_delimiters( span: Span, ) -> SourceResult<()> { let short_fall = DELIM_SHORT_FALL.resolve(styles); - let axis = scaled!(ctx, styles, axis_height); + let font = find_math_font(ctx.engine, styles, span)?; + let axis = constant!(font, styles, axis_height); 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, styles, left, span) - .stretch_vertical(ctx, target, short_fall); - left.align_on_axis(ctx, delimiter_alignment(left.c)); + if let Some(left_c) = left { + let mut left = ctx.layout_into_glyph(left_c, span, styles)?; + left.stretch_vertical(ctx, target, short_fall); + left.align_on_axis(delimiter_alignment(left_c)); ctx.push(left); } ctx.push(FrameFragment::new(styles, frame)); - if let Some(right) = right { - let mut right = GlyphFragment::new(ctx, styles, right, span) - .stretch_vertical(ctx, target, short_fall); - right.align_on_axis(ctx, delimiter_alignment(right.c)); + if let Some(right_c) = right { + let mut right = ctx.layout_into_glyph(right_c, span, styles)?; + right.stretch_vertical(ctx, target, short_fall); + right.align_on_axis(delimiter_alignment(right_c)); ctx.push(right); } diff --git a/crates/typst-layout/src/math/mod.rs b/crates/typst-layout/src/math/mod.rs index 708a4443d..9ca02c292 100644 --- a/crates/typst-layout/src/math/mod.rs +++ b/crates/typst-layout/src/math/mod.rs @@ -13,9 +13,7 @@ mod stretch; mod text; mod underover; -use rustybuzz::Feature; -use ttf_parser::Tag; -use typst_library::diag::{bail, SourceResult}; +use typst_library::diag::SourceResult; use typst_library::engine::Engine; use typst_library::foundations::{ Content, NativeElement, Packed, Resolve, StyleChain, SymbolElem, @@ -29,20 +27,17 @@ use typst_library::layout::{ use typst_library::math::*; use typst_library::model::ParElem; use typst_library::routines::{Arenas, RealizationKind}; -use typst_library::text::{ - families, features, variant, Font, LinebreakElem, SpaceElem, TextEdgeBounds, TextElem, -}; -use typst_library::World; +use typst_library::text::{LinebreakElem, SpaceElem, TextEdgeBounds, TextElem}; use typst_syntax::Span; use typst_utils::Numeric; use unicode_math_class::MathClass; use self::fragment::{ - FrameFragment, GlyphFragment, GlyphwiseSubsts, Limits, MathFragment, VariantFragment, + has_dtls_feat, stretch_axes, FrameFragment, GlyphFragment, Limits, MathFragment, }; use self::run::{LeftRightAlternator, MathRun, MathRunFrameBuilder}; use self::shared::*; -use self::stretch::{stretch_fragment, stretch_glyph}; +use self::stretch::stretch_fragment; /// Layout an inline equation (in a paragraph). #[typst_macros::time(span = elem.span())] @@ -55,12 +50,11 @@ pub fn layout_equation_inline( ) -> SourceResult> { assert!(!elem.block(styles)); - let font = find_math_font(engine, styles, elem.span())?; - let mut locator = locator.split(); - let mut ctx = MathContext::new(engine, &mut locator, styles, region, &font); + let mut ctx = MathContext::new(engine, &mut locator, region); - let scale_style = style_for_script_scale(&ctx); + let font = find_math_font(ctx.engine, styles, elem.span())?; + let scale_style = style_for_script_scale(&font); let styles = styles.chain(&scale_style); let run = ctx.layout_into_run(&elem.body, styles)?; @@ -110,12 +104,12 @@ pub fn layout_equation_block( assert!(elem.block(styles)); let span = elem.span(); - let font = find_math_font(engine, styles, span)?; let mut locator = locator.split(); - let mut ctx = MathContext::new(engine, &mut locator, styles, regions.base(), &font); + let mut ctx = MathContext::new(engine, &mut locator, regions.base()); - let scale_style = style_for_script_scale(&ctx); + let font = find_math_font(ctx.engine, styles, elem.span())?; + let scale_style = style_for_script_scale(&font); let styles = styles.chain(&scale_style); let full_equation_builder = ctx @@ -236,24 +230,6 @@ pub fn layout_equation_block( Ok(Fragment::frames(frames)) } -fn find_math_font( - engine: &mut Engine<'_>, - styles: StyleChain, - span: Span, -) -> SourceResult { - let variant = variant(styles); - let world = engine.world; - let Some(font) = families(styles).find_map(|family| { - let id = world.book().select(family.as_str(), variant)?; - let font = world.font(id)?; - let _ = font.ttf().tables().math?.constants?; - Some(font) - }) else { - bail!(span, "current font does not support math"); - }; - Ok(font) -} - fn add_equation_number( equation_builder: MathRunFrameBuilder, number: Frame, @@ -372,16 +348,6 @@ struct MathContext<'a, 'v, 'e> { engine: &'v mut Engine<'e>, locator: &'v mut SplitLocator<'a>, region: Region, - // Font-related. - font: &'a Font, - ttf: &'a ttf_parser::Face<'a>, - table: ttf_parser::math::Table<'a>, - constants: ttf_parser::math::Constants<'a>, - dtls_table: Option>, - flac_table: Option>, - ssty_table: Option>, - glyphwise_tables: Option>>, - space_width: Em, // Mutable. fragments: Vec, } @@ -391,46 +357,12 @@ impl<'a, 'v, 'e> MathContext<'a, 'v, 'e> { fn new( engine: &'v mut Engine<'e>, locator: &'v mut SplitLocator<'a>, - styles: StyleChain<'a>, base: Size, - font: &'a Font, ) -> Self { - let math_table = font.ttf().tables().math.unwrap(); - let gsub_table = font.ttf().tables().gsub; - let constants = math_table.constants.unwrap(); - - let feat = |tag: &[u8; 4]| { - GlyphwiseSubsts::new(gsub_table, Feature::new(Tag::from_bytes(tag), 0, ..)) - }; - - let features = features(styles); - let glyphwise_tables = Some( - features - .into_iter() - .filter_map(|feature| GlyphwiseSubsts::new(gsub_table, feature)) - .collect(), - ); - - 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); - Self { engine, locator, region: Region::new(base, Axes::splat(false)), - font, - ttf, - table: math_table, - constants, - dtls_table: feat(b"dtls"), - flac_table: feat(b"flac"), - ssty_table: feat(b"ssty"), - glyphwise_tables, - space_width, fragments: vec![], } } @@ -469,6 +401,23 @@ impl<'a, 'v, 'e> MathContext<'a, 'v, 'e> { Ok(std::mem::replace(&mut self.fragments, prev)) } + fn layout_into_glyph( + &mut self, + c: char, + span: Span, + styles: StyleChain, + ) -> SourceResult { + let prev = std::mem::take(&mut self.fragments); + let elem = SymbolElem::packed(c).spanned(span); + self.layout_into_self(&elem, styles)?; + let MathFragment::Glyph(glyph) = + std::mem::replace(&mut self.fragments, prev).remove(0) + else { + unreachable!() + }; + Ok(glyph) + } + /// Layout the given element and return the result as a /// unified [`MathFragment`]. fn layout_into_fragment( @@ -504,15 +453,7 @@ impl<'a, 'v, 'e> MathContext<'a, 'v, 'e> { styles, )?; - let outer = styles; for (elem, styles) in pairs { - // Hack because the font is fixed in math. - if styles != outer && TextElem::font_in(styles) != TextElem::font_in(outer) { - let frame = layout_external(elem, self, styles)?; - self.push(FrameFragment::new(styles, frame).with_spaced(true)); - continue; - } - layout_realized(elem, self, styles)?; } @@ -529,7 +470,9 @@ fn layout_realized( if let Some(elem) = elem.to_packed::() { ctx.push(MathFragment::Tag(elem.tag.clone())); } else if elem.is::() { - ctx.push(MathFragment::Space(ctx.space_width.resolve(styles))); + let font = find_math_font(ctx.engine, styles, elem.span())?; + let space_width = font.space_width().unwrap_or(THICK).resolve(styles); + ctx.push(MathFragment::Space(space_width)); } else if elem.is::() { ctx.push(MathFragment::Linebreak); } else if let Some(elem) = elem.to_packed::() { @@ -599,7 +542,8 @@ fn layout_realized( } else { let mut frame = layout_external(elem, ctx, styles)?; if !frame.has_baseline() { - let axis = scaled!(ctx, styles, axis_height); + let font = find_math_font(ctx.engine, styles, elem.span())?; + let axis = constant!(font, styles, axis_height); frame.set_baseline(frame.height() / 2.0 + axis); } ctx.push( diff --git a/crates/typst-layout/src/math/root.rs b/crates/typst-layout/src/math/root.rs index c7f41488e..635d9e40b 100644 --- a/crates/typst-layout/src/math/root.rs +++ b/crates/typst-layout/src/math/root.rs @@ -2,10 +2,9 @@ use typst_library::diag::SourceResult; use typst_library::foundations::{Packed, StyleChain}; use typst_library::layout::{Abs, Frame, FrameItem, Point, Size}; use typst_library::math::{EquationElem, MathSize, RootElem}; -use typst_library::text::TextElem; use typst_library::visualize::{FixedStroke, Geometry}; -use super::{style_cramped, FrameFragment, GlyphFragment, MathContext}; +use super::{find_math_font, style_cramped, FrameFragment, MathContext}; /// Lays out a [`RootElem`]. /// @@ -17,20 +16,8 @@ pub fn layout_root( ctx: &mut MathContext, styles: StyleChain, ) -> SourceResult<()> { - let index = elem.index(styles); let span = elem.span(); - let gap = scaled!( - ctx, styles, - text: radical_vertical_gap, - display: radical_display_style_vertical_gap, - ); - 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. let radicand = { let cramped = style_cramped(); @@ -39,23 +26,41 @@ pub fn layout_root( let multiline = run.is_multiline(); let mut radicand = run.into_fragment(styles).into_frame(); if multiline { + let font = find_math_font(ctx.engine, styles, elem.span())?; + let axis = constant!(font, styles, axis_height); // Align the frame center line with the math axis. - radicand.set_baseline( - radicand.height() / 2.0 + scaled!(ctx, styles, axis_height), - ); + radicand.set_baseline(radicand.height() / 2.0 + axis); } radicand }; // Layout root symbol. + let mut sqrt = ctx.layout_into_glyph('√', span, styles)?; + let gap = value!( + sqrt.text, styles, + inline: radical_vertical_gap, + display: radical_display_style_vertical_gap, + ); + let thickness = value!(sqrt.text, radical_rule_thickness); + let extra_ascender = value!(sqrt.text, radical_extra_ascender); + let kern_before = value!(sqrt.text, radical_kern_before_degree); + let kern_after = value!(sqrt.text, radical_kern_after_degree); + let raise_factor = percent!(sqrt.text, radical_degree_bottom_raise_percent); + + let line = FrameItem::Shape( + Geometry::Line(Point::with_x(radicand.width())) + .stroked(FixedStroke::from_pair(sqrt.text.fill.clone(), thickness)), + span, + ); + let target = radicand.height() + thickness + gap; - let sqrt = GlyphFragment::new(ctx, styles, '√', span) - .stretch_vertical(ctx, target, Abs::zero()) - .frame; + sqrt.stretch_vertical(ctx, target, Abs::zero()); + let sqrt = sqrt.into_frame(); // Layout the index. let sscript = EquationElem::set_size(MathSize::ScriptScript).wrap(); - let index = index + let index = elem + .index(styles) .as_ref() .map(|elem| ctx.layout_into_frame(elem, styles.chain(&sscript))) .transpose()?; @@ -107,19 +112,7 @@ pub fn layout_root( } frame.push_frame(sqrt_pos, sqrt); - frame.push( - line_pos, - FrameItem::Shape( - Geometry::Line(Point::with_x(radicand.width())).stroked( - FixedStroke::from_pair( - TextElem::fill_in(styles).as_decoration(), - thickness, - ), - ), - span, - ), - ); - + frame.push(line_pos, line); frame.push_frame(radicand_pos, radicand); ctx.push(FrameFragment::new(styles, frame)); diff --git a/crates/typst-layout/src/math/shared.rs b/crates/typst-layout/src/math/shared.rs index 600c130d4..ec35a1dc1 100644 --- a/crates/typst-layout/src/math/shared.rs +++ b/crates/typst-layout/src/math/shared.rs @@ -1,57 +1,103 @@ -use ttf_parser::math::MathValue; +use ttf_parser::Tag; +use typst_library::diag::{bail, SourceResult}; +use typst_library::engine::Engine; use typst_library::foundations::{Style, StyleChain}; use typst_library::layout::{Abs, Em, FixedAlignment, Frame, Point, Size, VAlignment}; use typst_library::math::{EquationElem, MathSize}; +use typst_library::text::{families, variant, Font, FontFeatures, TextElem}; +use typst_library::World; +use typst_syntax::Span; use typst_utils::LazyHash; -use super::{LeftRightAlternator, MathContext, MathFragment, MathRun}; +use super::{LeftRightAlternator, MathFragment, MathRun}; -macro_rules! scaled { - ($ctx:expr, $styles:expr, text: $text:ident, display: $display:ident $(,)?) => { - match typst_library::math::EquationElem::size_in($styles) { - typst_library::math::MathSize::Display => scaled!($ctx, $styles, $display), - _ => scaled!($ctx, $styles, $text), - } - }; - ($ctx:expr, $styles:expr, $name:ident) => { - $crate::math::Scaled::scaled( - $ctx.constants.$name(), - $ctx, - typst_library::text::TextElem::size_in($styles), - ) +macro_rules! percent { + ($text:expr, $name:ident) => { + $text + .font + .ttf() + .tables() + .math + .and_then(|math| math.constants) + .map(|constants| constants.$name()) + .unwrap() as f64 + / 100.0 }; } -macro_rules! percent { - ($ctx:expr, $name:ident) => { - $ctx.constants.$name() as f64 / 100.0 +macro_rules! word { + ($text:expr, $name:ident) => { + $text + .font + .ttf() + .tables() + .math + .and_then(|math| math.constants) + .map(|constants| $text.font.to_em(constants.$name()).at($text.size)) + .unwrap() + }; +} + +macro_rules! value { + ($text:expr, $styles:expr, inline: $inline:ident, display: $display:ident $(,)?) => { + match typst_library::math::EquationElem::size_in($styles) { + typst_library::math::MathSize::Display => value!($text, $display), + _ => value!($text, $inline), + } + }; + ($text:expr, $name:ident) => { + $text + .font + .ttf() + .tables() + .math + .and_then(|math| math.constants) + .map(|constants| $text.font.to_em(constants.$name().value).at($text.size)) + .unwrap() + }; +} + +macro_rules! constant { + ($font:expr, $styles:expr, text: $text:ident, display: $display:ident $(,)?) => { + match typst_library::math::EquationElem::size_in($styles) { + typst_library::math::MathSize::Display => constant!($font, $styles, $display), + _ => constant!($font, $styles, $text), + } + }; + ($font:expr, $styles:expr, $name:ident) => { + typst_library::foundations::Resolve::resolve( + $font + .ttf() + .tables() + .math + .and_then(|math| math.constants) + .map(|constants| $font.to_em(constants.$name().value)) + .unwrap(), + $styles, + ) }; } /// How much less high scaled delimiters can be than what they wrap. pub const DELIM_SHORT_FALL: Em = Em::new(0.1); -/// Converts some unit to an absolute length with the current font & font size. -pub trait Scaled { - fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs; -} - -impl Scaled for i16 { - 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, font_size: Abs) -> Abs { - ctx.font.to_em(self).at(font_size) - } -} - -impl Scaled for MathValue<'_> { - fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs { - self.value.scaled(ctx, font_size) - } +pub fn find_math_font( + engine: &mut Engine<'_>, + styles: StyleChain, + span: Span, +) -> SourceResult { + let variant = variant(styles); + let world = engine.world; + let Some(font) = families(styles).find_map(|family| { + let id = world.book().select(family.as_str(), variant)?; + let font = world.font(id)?; + let _ = font.ttf().tables().math?.constants?; + // Take the base font as the "main" math font. + family.covers().map_or(Some(font), |_| None) + }) else { + bail!(span, "current font does not support math"); + }; + Ok(font) } /// Styles something as cramped. @@ -59,6 +105,14 @@ pub fn style_cramped() -> LazyHash