diff --git a/library/src/math/braced.rs b/library/src/math/braced.rs deleted file mode 100644 index e207cb924..000000000 --- a/library/src/math/braced.rs +++ /dev/null @@ -1,123 +0,0 @@ -use super::*; - -const BRACED_GAP: Em = Em::new(0.3); - -/// # Underbrace -/// A horizontal brace under content, with an optional annotation below. -/// -/// ## Example -/// ``` -/// $ underbrace(1 + 2 + ... + 5, "numbers") $ -/// ``` -/// -/// ## Parameters -/// - body: Content (positional, required) -/// The content above the brace. -/// -/// - annotation: Content (positional) -/// The optional content below the brace. -/// -/// ## Category -/// math -#[func] -#[capable(LayoutMath)] -#[derive(Debug, Hash)] -pub struct UnderbraceNode { - /// The content above the brace. - pub body: Content, - /// The optional content below the brace. - pub annotation: Option, -} - -#[node] -impl UnderbraceNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { - let body = args.expect("body")?; - let annotation = args.eat()?; - Ok(Self { body, annotation }.pack()) - } -} - -impl LayoutMath for UnderbraceNode { - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - let gap = BRACED_GAP.scaled(ctx); - let body = ctx.layout_row(&self.body)?; - let glyph = GlyphFragment::new(ctx, '⏟'); - let brace = glyph.stretch_horizontal(ctx, body.width(), Abs::zero()); - - let mut rows = vec![body, brace.into()]; - ctx.style(ctx.style.for_subscript()); - rows.extend( - self.annotation - .as_ref() - .map(|annotation| ctx.layout_row(annotation)) - .transpose()?, - ); - ctx.unstyle(); - ctx.push(stack(ctx, rows, Align::Center, gap, 0)); - - Ok(()) - } -} - -/// # Overbrace -/// A horizontal brace over content, with an optional annotation above. -/// -/// ## Example -/// ``` -/// $ overbrace(1 + 2 + ... + 5, "numbers") $ -/// ``` -/// -/// ## Parameters -/// - body: Content (positional, required) -/// The content below the brace. -/// -/// - annotation: Content (positional) -/// The optional content above the brace. -/// -/// ## Category -/// math -#[func] -#[capable(LayoutMath)] -#[derive(Debug, Hash)] -pub struct OverbraceNode { - /// The content below the brace. - pub body: Content, - /// The optional content above the brace. - pub annotation: Option, -} - -#[node] -impl OverbraceNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { - let body = args.expect("body")?; - let annotation = args.eat()?; - Ok(Self { body, annotation }.pack()) - } -} - -impl LayoutMath for OverbraceNode { - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - let gap = BRACED_GAP.scaled(ctx); - let body = ctx.layout_row(&self.body)?; - let glyph = GlyphFragment::new(ctx, '⏞'); - let brace = glyph.stretch_horizontal(ctx, body.width(), Abs::zero()); - - let mut rows = vec![]; - ctx.style(ctx.style.for_superscript()); - rows.extend( - self.annotation - .as_ref() - .map(|annotation| ctx.layout_row(annotation)) - .transpose()?, - ); - ctx.unstyle(); - rows.push(brace.into()); - rows.push(body); - - let last = rows.len() - 1; - ctx.push(stack(ctx, rows, Align::Center, gap, last)); - - Ok(()) - } -} diff --git a/library/src/math/matrix.rs b/library/src/math/matrix.rs index 27e1b282c..45ebdda7a 100644 --- a/library/src/math/matrix.rs +++ b/library/src/math/matrix.rs @@ -172,61 +172,20 @@ fn layout( let mut frame = stack(ctx, rows, align, gap, 0); let height = frame.height(); + let target = height + VERTICAL_PADDING.of(height); frame.set_baseline(frame.height() / 2.0 + axis); if let Some(left) = left { - ctx.push(GlyphFragment::new(ctx, left).stretch_vertical(ctx, height, short_fall)); + ctx.push(GlyphFragment::new(ctx, left).stretch_vertical(ctx, target, short_fall)); } ctx.push(frame); if let Some(right) = right { ctx.push( - GlyphFragment::new(ctx, right).stretch_vertical(ctx, height, short_fall), + GlyphFragment::new(ctx, right).stretch_vertical(ctx, target, short_fall), ); } Ok(()) } - -/// Stack rows on top of each other. -/// -/// Add a `gap` between each row and uses the baseline of the `baseline`th -/// row for the whole frame. -pub(super) fn stack( - ctx: &MathContext, - rows: Vec, - align: Align, - gap: Abs, - baseline: usize, -) -> Frame { - let mut width = Abs::zero(); - let mut height = rows.len().saturating_sub(1) as f64 * gap; - - let points = alignments(&rows); - let rows: Vec<_> = - rows.into_iter().map(|row| row.to_line_frame(ctx, &points)).collect(); - - for row in &rows { - height += row.height(); - width.set_max(row.width()); - } - - let extra = VERTICAL_PADDING.of(height); - height += extra; - - let mut y = extra / 2.0; - let mut frame = Frame::new(Size::new(width, height)); - - for (i, row) in rows.into_iter().enumerate() { - let x = align.position(width - row.width()); - let pos = Point::new(x, y); - if i == baseline { - frame.set_baseline(y + row.baseline()); - } - y += row.height() + gap; - frame.push_frame(pos, row); - } - - frame -} diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index 4c002cead..2d914c319 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -5,7 +5,6 @@ mod ctx; mod accent; mod align; mod attach; -mod braced; mod frac; mod fragment; mod lr; @@ -14,6 +13,7 @@ mod op; mod root; mod row; mod spacing; +mod stack; mod stretch; mod style; mod symbols; @@ -21,12 +21,12 @@ mod symbols; pub use self::accent::*; pub use self::align::*; pub use self::attach::*; -pub use self::braced::*; pub use self::frac::*; pub use self::lr::*; pub use self::matrix::*; pub use self::op::*; pub use self::root::*; +pub use self::stack::*; pub use self::style::*; use ttf_parser::GlyphId; @@ -65,8 +65,12 @@ pub fn module(sym: &Module) -> Module { math.def_func::("scripts"); math.def_func::("limits"); math.def_func::("accent"); + math.def_func::("underline"); + math.def_func::("overline"); math.def_func::("underbrace"); math.def_func::("overbrace"); + math.def_func::("underbracket"); + math.def_func::("overbracket"); // Fractions and matrix-likes. math.def_func::("frac"); diff --git a/library/src/math/stack.rs b/library/src/math/stack.rs new file mode 100644 index 000000000..c8a1252c0 --- /dev/null +++ b/library/src/math/stack.rs @@ -0,0 +1,315 @@ +use super::*; + +const LINE_GAP: Em = Em::new(0.15); +const BRACE_GAP: Em = Em::new(0.25); +const BRACKET_GAP: Em = Em::new(0.25); + +/// # Underline +/// A horizontal line under content. +/// +/// ## Example +/// ``` +/// $ underline(1 + 2 + ... + 5) $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The content above the line. +/// +/// ## Category +/// math +#[func] +#[capable(LayoutMath)] +#[derive(Debug, Hash)] +pub struct UnderlineNode(Content); + +#[node] +impl UnderlineNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + Ok(Self(args.expect("body")?).pack()) + } +} + +impl LayoutMath for UnderlineNode { + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + layout(ctx, &self.0, &None, '\u{305}', LINE_GAP, false) + } +} + +/// # Overline +/// A horizontal line over content. +/// +/// ## Example +/// ``` +/// $ overline(1 + 2 + ... + 5) $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The content below the line. +/// +/// ## Category +/// math +#[func] +#[capable(LayoutMath)] +#[derive(Debug, Hash)] +pub struct OverlineNode(Content); + +#[node] +impl OverlineNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + Ok(Self(args.expect("body")?).pack()) + } +} + +impl LayoutMath for OverlineNode { + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + layout(ctx, &self.0, &None, '\u{332}', LINE_GAP, true) + } +} + +/// # Underbrace +/// A horizontal brace under content, with an optional annotation below. +/// +/// ## Example +/// ``` +/// $ underbrace(1 + 2 + ... + 5, "numbers") $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The content above the brace. +/// +/// - annotation: Content (positional) +/// The optional content below the brace. +/// +/// ## Category +/// math +#[func] +#[capable(LayoutMath)] +#[derive(Debug, Hash)] +pub struct UnderbraceNode { + /// The content above the brace. + pub body: Content, + /// The optional content below the brace. + pub annotation: Option, +} + +#[node] +impl UnderbraceNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + let body = args.expect("body")?; + let annotation = args.eat()?; + Ok(Self { body, annotation }.pack()) + } +} + +impl LayoutMath for UnderbraceNode { + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + layout(ctx, &self.body, &self.annotation, '⏟', BRACE_GAP, false) + } +} + +/// # Overbrace +/// A horizontal brace over content, with an optional annotation above. +/// +/// ## Example +/// ``` +/// $ overbrace(1 + 2 + ... + 5, "numbers") $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The content below the brace. +/// +/// - annotation: Content (positional) +/// The optional content above the brace. +/// +/// ## Category +/// math +#[func] +#[capable(LayoutMath)] +#[derive(Debug, Hash)] +pub struct OverbraceNode { + /// The content below the brace. + pub body: Content, + /// The optional content above the brace. + pub annotation: Option, +} + +#[node] +impl OverbraceNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + let body = args.expect("body")?; + let annotation = args.eat()?; + Ok(Self { body, annotation }.pack()) + } +} + +impl LayoutMath for OverbraceNode { + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + layout(ctx, &self.body, &self.annotation, '⏞', BRACE_GAP, true) + } +} + +/// # Underbracket +/// A horizontal bracket under content, with an optional annotation below. +/// +/// ## Example +/// ``` +/// $ underbracket(1 + 2 + ... + 5, "numbers") $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The content above the bracket. +/// +/// - annotation: Content (positional) +/// The optional content below the bracket. +/// +/// ## Category +/// math +#[func] +#[capable(LayoutMath)] +#[derive(Debug, Hash)] +pub struct UnderbracketNode { + /// The content above the bracket. + pub body: Content, + /// The optional content below the bracket. + pub annotation: Option, +} + +#[node] +impl UnderbracketNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + let body = args.expect("body")?; + let annotation = args.eat()?; + Ok(Self { body, annotation }.pack()) + } +} + +impl LayoutMath for UnderbracketNode { + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + layout(ctx, &self.body, &self.annotation, '⎵', BRACKET_GAP, false) + } +} + +/// # Overbracket +/// A horizontal bracket over content, with an optional annotation above. +/// +/// ## Example +/// ``` +/// $ overbracket(1 + 2 + ... + 5, "numbers") $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The content below the bracket. +/// +/// - annotation: Content (positional) +/// The optional content above the bracket. +/// +/// ## Category +/// math +#[func] +#[capable(LayoutMath)] +#[derive(Debug, Hash)] +pub struct OverbracketNode { + /// The content below the bracket. + pub body: Content, + /// The optional content above the bracket. + pub annotation: Option, +} + +#[node] +impl OverbracketNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + let body = args.expect("body")?; + let annotation = args.eat()?; + Ok(Self { body, annotation }.pack()) + } +} + +impl LayoutMath for OverbracketNode { + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + layout(ctx, &self.body, &self.annotation, '⎴', BRACKET_GAP, true) + } +} + +/// Layout an over- or underthing. +fn layout( + ctx: &mut MathContext, + body: &Content, + annotation: &Option, + c: char, + gap: Em, + reverse: bool, +) -> SourceResult<()> { + let gap = gap.scaled(ctx); + let body = ctx.layout_row(body)?; + let glyph = GlyphFragment::new(ctx, c); + let stretched = glyph.stretch_horizontal(ctx, body.width(), Abs::zero()); + + let mut rows = vec![body, stretched.into()]; + ctx.style(if reverse { + ctx.style.for_subscript() + } else { + ctx.style.for_superscript() + }); + rows.extend( + annotation + .as_ref() + .map(|annotation| ctx.layout_row(annotation)) + .transpose()?, + ); + ctx.unstyle(); + + let mut baseline = 0; + if reverse { + rows.reverse(); + baseline = rows.len() - 1; + } + + ctx.push(stack(ctx, rows, Align::Center, gap, baseline)); + + Ok(()) +} + +/// Stack rows on top of each other. +/// +/// Add a `gap` between each row and uses the baseline of the `baseline`th +/// row for the whole frame. +pub(super) fn stack( + ctx: &MathContext, + rows: Vec, + align: Align, + gap: Abs, + baseline: usize, +) -> Frame { + let mut width = Abs::zero(); + let mut height = rows.len().saturating_sub(1) as f64 * gap; + + let points = alignments(&rows); + let rows: Vec<_> = + rows.into_iter().map(|row| row.to_line_frame(ctx, &points)).collect(); + + for row in &rows { + height += row.height(); + width.set_max(row.width()); + } + + let mut y = Abs::zero(); + let mut frame = Frame::new(Size::new(width, height)); + + for (i, row) in rows.into_iter().enumerate() { + let x = align.position(width - row.width()); + let pos = Point::new(x, y); + if i == baseline { + frame.set_baseline(y + row.baseline()); + } + y += row.height() + gap; + frame.push_frame(pos, row); + } + + frame +} diff --git a/library/src/text/symbols.rs b/library/src/text/symbols.rs index 36e49849e..8bc6222a8 100644 --- a/library/src/text/symbols.rs +++ b/library/src/text/symbols.rs @@ -150,13 +150,11 @@ symbols! { breve: '˘', caret: '‸', caron: 'ˇ', - cedilla: '¸', circum: '^', diaer: '¨', grave: '`', macron: '¯', tilde: '~', - overline: '‾', // Currency. bitcoin: '₿', diff --git a/src/model/symbol.rs b/src/model/symbol.rs index 214fea3e8..146f7502a 100644 --- a/src/model/symbol.rs +++ b/src/model/symbol.rs @@ -174,12 +174,8 @@ pub fn combining_accent(c: char) -> Option { '\u{030a}' | '∘' | '○' => '\u{030a}', '\u{030b}' | '˝' => '\u{030b}', '\u{030c}' | 'ˇ' => '\u{030c}', - '\u{0327}' | '¸' => '\u{0327}', - '\u{0328}' | '˛' => '\u{0328}', - '\u{0332}' | '_' => '\u{0332}', '\u{20d6}' | '←' => '\u{20d6}', '\u{20d7}' | '→' | '⟶' => '\u{20d7}', - '⏞' | '⏟' | '⎴' | '⎵' | '⏜' | '⏝' | '⏠' | '⏡' => c, _ => return None, }) } diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs index d267a05bd..cbddabb76 100644 --- a/src/syntax/lexer.rs +++ b/src/syntax/lexer.rs @@ -181,7 +181,7 @@ impl Lexer<'_> { '*' if !self.in_word() => SyntaxKind::Star, '_' if !self.in_word() => SyntaxKind::Underscore, - '#' if !self.s.at(char::is_whitespace) => SyntaxKind::Hashtag, + '#' if self.s.at(|c: char| !c.is_whitespace()) => SyntaxKind::Hashtag, '[' => SyntaxKind::LeftBracket, ']' => SyntaxKind::RightBracket, '\'' => SyntaxKind::SmartQuote, @@ -389,7 +389,6 @@ impl Lexer<'_> { '<' if self.s.eat_if('<') => SyntaxKind::Shorthand, '>' if self.s.eat_if(">>") => SyntaxKind::Shorthand, '>' if self.s.eat_if('>') => SyntaxKind::Shorthand, - '<' if self.s.eat_if("=>") => SyntaxKind::Shorthand, '<' if self.s.eat_if("->") => SyntaxKind::Shorthand, '<' if self.s.eat_if('=') => SyntaxKind::Shorthand, @@ -397,13 +396,12 @@ impl Lexer<'_> { '<' if self.s.eat_if('-') => SyntaxKind::Shorthand, '-' if self.s.eat_if('>') => SyntaxKind::Shorthand, '=' if self.s.eat_if('>') => SyntaxKind::Shorthand, - ':' if self.s.eat_if('=') => SyntaxKind::Shorthand, '[' if self.s.eat_if('|') => SyntaxKind::Shorthand, '|' if self.s.eat_if(']') => SyntaxKind::Shorthand, '|' if self.s.eat_if('|') => SyntaxKind::Shorthand, - '#' if !self.s.at(char::is_whitespace) => SyntaxKind::Hashtag, + '#' if self.s.at(|c: char| !c.is_whitespace()) => SyntaxKind::Hashtag, '_' => SyntaxKind::Underscore, '$' => SyntaxKind::Dollar, '/' => SyntaxKind::Slash,