diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-01-27 15:09:05 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-01-27 15:32:05 +0100 |
| commit | 2e039cb052fcb768027053cbf02ce396f6d7a6be (patch) | |
| tree | b1a1c1da0440805b296e3204fa30cd9666322a0e /library/src | |
| parent | a59b9fff93f708d5a35d2bf61c3b21efee71b7e9 (diff) | |
Fix math spacing bugs
Diffstat (limited to 'library/src')
| -rw-r--r-- | library/src/math/atom.rs | 5 | ||||
| -rw-r--r-- | library/src/math/ctx.rs | 21 | ||||
| -rw-r--r-- | library/src/math/fragment.rs | 63 | ||||
| -rw-r--r-- | library/src/math/lr.rs | 4 | ||||
| -rw-r--r-- | library/src/math/mod.rs | 15 | ||||
| -rw-r--r-- | library/src/math/op.rs | 10 | ||||
| -rw-r--r-- | library/src/math/row.rs | 60 | ||||
| -rw-r--r-- | library/src/math/script.rs | 4 | ||||
| -rw-r--r-- | library/src/math/spacing.rs | 37 | ||||
| -rw-r--r-- | library/src/math/stretch.rs | 1 |
10 files changed, 166 insertions, 54 deletions
diff --git a/library/src/math/atom.rs b/library/src/math/atom.rs index 5b35d289..6d7359bf 100644 --- a/library/src/math/atom.rs +++ b/library/src/math/atom.rs @@ -31,7 +31,7 @@ impl LayoutMath for AtomNode { { // A single letter that is available in the math font. if ctx.style.size == MathSize::Display - && glyph.class() == Some(MathClass::Large) + && glyph.class == Some(MathClass::Large) { let height = scaled!(ctx, display_operator_min_height); ctx.push(glyph.stretch_vertical(ctx, height, Abs::zero())); @@ -49,7 +49,8 @@ impl LayoutMath for AtomNode { ctx.push(frame); } else { // Anything else is handled by Typst's standard text layout. - TextNode(self.0.clone()).pack().layout_math(ctx)?; + let frame = ctx.layout_non_math(&TextNode(self.0.clone()).pack())?; + ctx.push(FrameFragment::new(frame).with_class(MathClass::Alphabetic)); } Ok(()) diff --git a/library/src/math/ctx.rs b/library/src/math/ctx.rs index 41299f98..551d6cd7 100644 --- a/library/src/math/ctx.rs +++ b/library/src/math/ctx.rs @@ -30,6 +30,7 @@ pub(super) struct MathContext<'a, 'b, 'v> { pub ttf: &'a ttf_parser::Face<'a>, pub table: ttf_parser::math::Table<'a>, pub constants: ttf_parser::math::Constants<'a>, + pub space_width: Em, pub fill: Paint, pub lang: Lang, pub row: MathRow, @@ -50,6 +51,14 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { let table = font.ttf().tables().math.unwrap(); let constants = table.constants.unwrap(); let size = styles.get(TextNode::SIZE); + + 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 { vt, outer: styles, @@ -71,6 +80,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { ttf: font.ttf(), table, constants, + space_width, row: MathRow::new(), base_size: size, scaled_size: size, @@ -79,7 +89,16 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { } pub fn push(&mut self, fragment: impl Into<MathFragment>) { - self.row.push(self.scaled_size, self.style, fragment); + self.row + .push(self.scaled_size, self.space_width, self.style, fragment); + } + + pub fn extend(&mut self, row: MathRow) { + let mut iter = row.0.into_iter(); + if let Some(first) = iter.next() { + self.push(first); + } + self.row.0.extend(iter); } pub fn layout_non_math(&mut self, content: &Content) -> SourceResult<Frame> { diff --git a/library/src/math/fragment.rs b/library/src/math/fragment.rs index d6d55cc3..fef57a0a 100644 --- a/library/src/math/fragment.rs +++ b/library/src/math/fragment.rs @@ -6,8 +6,9 @@ pub(super) enum MathFragment { Variant(VariantFragment), Frame(FrameFragment), Spacing(Abs), - Align, + Space, Linebreak, + Align, } impl MathFragment { @@ -54,13 +55,26 @@ impl MathFragment { pub fn class(&self) -> Option<MathClass> { match self { - Self::Glyph(glyph) => glyph.class(), - Self::Variant(variant) => variant.class(), + Self::Glyph(glyph) => glyph.class, + Self::Variant(variant) => variant.class, Self::Frame(fragment) => Some(fragment.class), _ => None, } } + pub fn set_class(&mut self, class: MathClass) { + match self { + Self::Glyph(glyph) => glyph.class = Some(class), + Self::Variant(variant) => variant.class = Some(class), + Self::Frame(fragment) => fragment.class = class, + _ => {} + } + } + + pub fn participating(&self) -> bool { + !matches!(self, Self::Space | Self::Spacing(_) | Self::Align) + } + pub fn italics_correction(&self) -> Abs { match self { Self::Glyph(glyph) => glyph.italics_correction, @@ -99,7 +113,7 @@ impl From<FrameFragment> for MathFragment { impl From<Frame> for MathFragment { fn from(frame: Frame) -> Self { - Self::Frame(FrameFragment { frame, class: MathClass::Normal, limits: false }) + Self::Frame(FrameFragment::new(frame)) } } @@ -112,6 +126,7 @@ pub(super) struct GlyphFragment { pub ascent: Abs, pub descent: Abs, pub italics_correction: Abs, + pub class: Option<MathClass>, } impl GlyphFragment { @@ -144,6 +159,10 @@ impl GlyphFragment { ascent: bbox.y_max.scaled(ctx), descent: -bbox.y_min.scaled(ctx), italics_correction: italics, + class: match c { + ':' => Some(MathClass::Relation), + _ => unicode_math_class::class(c), + }, } } @@ -151,16 +170,13 @@ impl GlyphFragment { self.ascent + self.descent } - pub fn class(&self) -> Option<MathClass> { - unicode_math_class::class(self.c) - } - pub fn to_variant(&self, ctx: &MathContext) -> VariantFragment { VariantFragment { c: self.c, id: Some(self.id), frame: self.to_frame(ctx), italics_correction: self.italics_correction, + class: self.class, } } @@ -191,12 +207,7 @@ pub struct VariantFragment { pub id: Option<GlyphId>, pub frame: Frame, pub italics_correction: Abs, -} - -impl VariantFragment { - pub fn class(&self) -> Option<MathClass> { - unicode_math_class::class(self.c) - } + pub class: Option<MathClass>, } #[derive(Debug, Clone)] @@ -204,6 +215,30 @@ pub struct FrameFragment { pub frame: Frame, pub class: MathClass, pub limits: bool, + pub spaced: bool, +} + +impl FrameFragment { + pub fn new(frame: Frame) -> Self { + Self { + frame, + class: MathClass::Normal, + limits: false, + spaced: false, + } + } + + pub fn with_class(self, class: MathClass) -> Self { + Self { class, ..self } + } + + pub fn with_limits(self, limits: bool) -> Self { + Self { limits, ..self } + } + + pub fn with_spaced(self, spaced: bool) -> Self { + Self { spaced, ..self } + } } /// Look up the italics correction for a glyph. diff --git a/library/src/math/lr.rs b/library/src/math/lr.rs index 89d12380..d5951050 100644 --- a/library/src/math/lr.rs +++ b/library/src/math/lr.rs @@ -69,9 +69,7 @@ impl LayoutMath for LrNode { } } - for fragment in row.0 { - ctx.push(fragment); - } + ctx.extend(row); Ok(()) } diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index 1e7a6580..2cc8fa9e 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -43,6 +43,7 @@ use self::row::*; use self::spacing::*; use crate::layout::HNode; use crate::layout::ParNode; +use crate::layout::Spacing; use crate::prelude::*; use crate::text::LinebreakNode; use crate::text::TextNode; @@ -222,6 +223,7 @@ impl LayoutMath for FormulaNode { impl LayoutMath for Content { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { if self.is::<SpaceNode>() { + ctx.push(MathFragment::Space); return Ok(()); } @@ -230,6 +232,17 @@ impl LayoutMath for Content { return Ok(()); } + if let Some(node) = self.to::<HNode>() { + if let Spacing::Relative(rel) = node.amount { + if rel.rel.is_zero() { + ctx.push(MathFragment::Spacing( + rel.abs.resolve(ctx.outer.chain(&ctx.map)), + )); + } + } + return Ok(()); + } + if let Some(node) = self.to::<SequenceNode>() { for child in &node.0 { child.layout_math(ctx)?; @@ -242,7 +255,7 @@ impl LayoutMath for Content { } let frame = ctx.layout_non_math(self)?; - ctx.push(frame); + ctx.push(FrameFragment::new(frame).with_spaced(true)); Ok(()) } diff --git a/library/src/math/op.rs b/library/src/math/op.rs index cf1f9105..22daee65 100644 --- a/library/src/math/op.rs +++ b/library/src/math/op.rs @@ -39,11 +39,11 @@ impl OpNode { impl LayoutMath for OpNode { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { let frame = ctx.layout_non_math(&TextNode(self.text.clone()).pack())?; - ctx.push(FrameFragment { - frame, - class: MathClass::Large, - limits: self.limits, - }); + ctx.push( + FrameFragment::new(frame) + .with_class(MathClass::Large) + .with_limits(self.limits), + ); Ok(()) } } diff --git a/library/src/math/row.rs b/library/src/math/row.rs index f7b2b384..f75aed99 100644 --- a/library/src/math/row.rs +++ b/library/src/math/row.rs @@ -15,36 +15,60 @@ impl MathRow { pub fn push( &mut self, font_size: Abs, + space_width: Em, style: MathStyle, fragment: impl Into<MathFragment>, ) { - let fragment = fragment.into(); - if let Some(fragment_class) = fragment.class() { - for (i, prev) in self.0.iter().enumerate().rev() { - if matches!(prev, MathFragment::Align) { - continue; - } + let mut fragment = fragment.into(); + if !fragment.participating() { + self.0.push(fragment); + return; + } - let mut amount = Abs::zero(); - if let MathFragment::Glyph(glyph) = *prev { - if !glyph.italics_correction.is_zero() - && fragment_class != MathClass::Alphabetic - { - amount += glyph.italics_correction; - } + let mut space = false; + for (i, prev) in self.0.iter().enumerate().rev() { + if !prev.participating() { + space |= matches!(prev, MathFragment::Space); + if matches!(prev, MathFragment::Spacing(_)) { + break; } + continue; + } - if let Some(prev_class) = prev.class() { - amount += spacing(prev_class, fragment_class, style).at(font_size); + if fragment.class() == Some(MathClass::Vary) { + if matches!( + prev.class(), + Some( + MathClass::Normal + | MathClass::Alphabetic + | MathClass::Binary + | MathClass::Closing + | MathClass::Fence + | MathClass::Relation + ) + ) { + fragment.set_class(MathClass::Binary); } + } - if !amount.is_zero() { - self.0.insert(i + 1, MathFragment::Spacing(amount)); + let mut amount = Abs::zero(); + if let MathFragment::Glyph(glyph) = *prev { + if !glyph.italics_correction.is_zero() + && fragment.class() != Some(MathClass::Alphabetic) + { + amount += glyph.italics_correction; } + } + + amount += spacing(prev, &fragment, style, space, space_width).at(font_size); - break; + if !amount.is_zero() { + self.0.insert(i + 1, MathFragment::Spacing(amount)); } + + break; } + self.0.push(fragment); } diff --git a/library/src/math/script.rs b/library/src/math/script.rs index 671651d9..2c765fbf 100644 --- a/library/src/math/script.rs +++ b/library/src/math/script.rs @@ -139,7 +139,7 @@ fn scripts( frame.push_frame(base_pos, base.to_frame(ctx)); frame.push_frame(sub_pos, sub); frame.push_frame(sup_pos, sup); - ctx.push(FrameFragment { frame, class, limits: false }); + ctx.push(FrameFragment::new(frame).with_class(class)); Ok(()) } @@ -172,7 +172,7 @@ fn limits( frame.push_frame(base_pos, base.to_frame(ctx)); frame.push_frame(sub_pos, sub); frame.push_frame(sup_pos, sup); - ctx.push(FrameFragment { frame, class, limits: false }); + ctx.push(FrameFragment::new(frame).with_class(class)); Ok(()) } diff --git a/library/src/math/spacing.rs b/library/src/math/spacing.rs index cf86b698..7083c5e1 100644 --- a/library/src/math/spacing.rs +++ b/library/src/math/spacing.rs @@ -1,10 +1,10 @@ use super::*; -const ZERO: Em = Em::zero(); -const THIN: Em = Em::new(1.0 / 6.0); -const MEDIUM: Em = Em::new(2.0 / 9.0); -const THICK: Em = Em::new(5.0 / 18.0); -const QUAD: Em = Em::new(1.0); +pub(super) const ZERO: Em = Em::zero(); +pub(super) const THIN: Em = Em::new(1.0 / 6.0); +pub(super) const MEDIUM: Em = Em::new(2.0 / 9.0); +pub(super) const THICK: Em = Em::new(5.0 / 18.0); +pub(super) const QUAD: Em = Em::new(1.0); /// Hook up all spacings. pub(super) fn define(math: &mut Scope) { @@ -15,10 +15,20 @@ pub(super) fn define(math: &mut Scope) { } /// Determine the spacing between two fragments in a given style. -pub(super) fn spacing(left: MathClass, right: MathClass, style: MathStyle) -> Em { +pub(super) fn spacing( + left: &MathFragment, + right: &MathFragment, + style: MathStyle, + space: bool, + space_width: Em, +) -> Em { use MathClass::*; let script = style.size <= MathSize::Script; - match (left, right) { + let (Some(l), Some(r)) = (left.class(), right.class()) else { + return ZERO; + }; + + match (l, r) { // No spacing before punctuation; thin spacing after punctuation, unless // in script size. (_, Punctuation) => ZERO, @@ -33,12 +43,23 @@ pub(super) fn spacing(left: MathClass, right: MathClass, style: MathStyle) -> Em (Relation, _) | (_, Relation) if !script => THICK, // Medium spacing around binary operators, unless in script size. - (Vary | Binary, _) | (_, Vary | Binary) if !script => MEDIUM, + (Binary, _) | (_, Binary) if !script => MEDIUM, // Thin spacing around large operators, unless next to a delimiter. (Large, Opening | Fence) | (Closing | Fence, Large) => ZERO, (Large, _) | (_, Large) => THIN, + // Spacing around spaced frames. + _ if space && (is_spaced(left) || is_spaced(right)) => space_width, + _ => ZERO, } } + +/// Whether this fragment should react to adjacent spaces. +fn is_spaced(fragment: &MathFragment) -> bool { + match fragment { + MathFragment::Frame(frame) => frame.spaced, + _ => fragment.class() == Some(MathClass::Fence), + } +} diff --git a/library/src/math/stretch.rs b/library/src/math/stretch.rs index bd72a769..aee226d1 100644 --- a/library/src/math/stretch.rs +++ b/library/src/math/stretch.rs @@ -178,6 +178,7 @@ fn assemble( id: None, frame, italics_correction: Abs::zero(), + class: base.class, } } |
